Coverage Report

Created: 2026-09-28 08:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wasmtime/cranelift/codegen/src/isa/unwind/systemv.rs
Line
Count
Source
1
//! System V ABI unwind information.
2
3
use crate::isa::unwind::UnwindInst;
4
use crate::machinst::Reg;
5
use crate::result::CodegenResult;
6
use crate::{CodegenError, binemit::CodeOffset};
7
use alloc::vec::Vec;
8
use gimli::write::{Address, FrameDescriptionEntry};
9
10
#[cfg(feature = "enable-serde")]
11
use serde_derive::{Deserialize, Serialize};
12
13
type Register = u16;
14
15
/// Enumerate the errors possible in mapping Cranelift registers to their DWARF equivalent.
16
#[expect(missing_docs, reason = "self-describing variants")]
17
#[derive(Debug, PartialEq, Eq)]
18
pub enum RegisterMappingError {
19
    MissingBank,
20
    UnsupportedArchitecture,
21
    UnsupportedRegisterBank(&'static str),
22
}
23
24
// This is manually implementing Error and Display instead of using thiserror to reduce the amount
25
// of dependencies used by Cranelift.
26
impl core::error::Error for RegisterMappingError {}
27
28
impl core::fmt::Display for RegisterMappingError {
29
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
30
0
        match self {
31
0
            RegisterMappingError::MissingBank => write!(f, "unable to find bank for register info"),
32
0
            RegisterMappingError::UnsupportedArchitecture => write!(
33
0
                f,
34
                "register mapping is currently only implemented for x86_64"
35
            ),
36
0
            RegisterMappingError::UnsupportedRegisterBank(bank) => {
37
0
                write!(f, "unsupported register bank: {bank}")
38
            }
39
        }
40
0
    }
41
}
42
43
// This mirrors gimli's CallFrameInstruction, but is serializable
44
// This excludes CfaExpression, Expression, ValExpression due to
45
// https://github.com/gimli-rs/gimli/issues/513.
46
// TODO: if gimli ever adds serialization support, remove this type
47
#[derive(Clone, Debug, PartialEq, Eq)]
48
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
49
pub(crate) enum CallFrameInstruction {
50
    Cfa(Register, i32),
51
    CfaRegister(Register),
52
    CfaOffset(i32),
53
    Restore(Register),
54
    Undefined(Register),
55
    SameValue(Register),
56
    Offset(Register, i32),
57
    ValOffset(Register, i32),
58
    Register(Register, Register),
59
    RememberState,
60
    RestoreState,
61
    ArgsSize(u32),
62
    /// Enables or disables pointer authentication on aarch64 platforms post ARMv8.3.  This
63
    /// particular item maps to gimli::ValExpression(RA_SIGN_STATE, lit0/lit1).
64
    Aarch64SetPointerAuth {
65
        return_addresses: bool,
66
    },
67
}
68
69
impl From<gimli::write::CallFrameInstruction> for CallFrameInstruction {
70
0
    fn from(cfi: gimli::write::CallFrameInstruction) -> Self {
71
        use gimli::write::CallFrameInstruction;
72
73
0
        match cfi {
74
0
            CallFrameInstruction::Cfa(reg, offset) => Self::Cfa(reg.0, offset),
75
0
            CallFrameInstruction::CfaRegister(reg) => Self::CfaRegister(reg.0),
76
0
            CallFrameInstruction::CfaOffset(offset) => Self::CfaOffset(offset),
77
0
            CallFrameInstruction::Restore(reg) => Self::Restore(reg.0),
78
0
            CallFrameInstruction::Undefined(reg) => Self::Undefined(reg.0),
79
0
            CallFrameInstruction::SameValue(reg) => Self::SameValue(reg.0),
80
0
            CallFrameInstruction::Offset(reg, offset) => Self::Offset(reg.0, offset),
81
0
            CallFrameInstruction::ValOffset(reg, offset) => Self::ValOffset(reg.0, offset),
82
0
            CallFrameInstruction::Register(reg1, reg2) => Self::Register(reg1.0, reg2.0),
83
0
            CallFrameInstruction::RememberState => Self::RememberState,
84
0
            CallFrameInstruction::RestoreState => Self::RestoreState,
85
0
            CallFrameInstruction::ArgsSize(size) => Self::ArgsSize(size),
86
            _ => {
87
                // Cranelift's unwind support does not generate `CallFrameInstruction`s with
88
                // Expression at this moment, and it is not trivial to
89
                // serialize such instructions.
90
0
                panic!("CallFrameInstruction with Expression not supported");
91
            }
92
        }
93
0
    }
94
}
95
96
impl From<CallFrameInstruction> for gimli::write::CallFrameInstruction {
97
6.89M
    fn from(cfi: CallFrameInstruction) -> gimli::write::CallFrameInstruction {
98
        use CallFrameInstruction as ClifCfi;
99
        use gimli::{Register, write::CallFrameInstruction as GimliCfi, write::Expression};
100
101
6.89M
        match cfi {
102
0
            ClifCfi::Cfa(reg, offset) => GimliCfi::Cfa(Register(reg), offset),
103
1.17M
            ClifCfi::CfaRegister(reg) => GimliCfi::CfaRegister(Register(reg)),
104
1.17M
            ClifCfi::CfaOffset(offset) => GimliCfi::CfaOffset(offset),
105
0
            ClifCfi::Restore(reg) => GimliCfi::Restore(Register(reg)),
106
0
            ClifCfi::Undefined(reg) => GimliCfi::Undefined(Register(reg)),
107
0
            ClifCfi::SameValue(reg) => GimliCfi::SameValue(Register(reg)),
108
4.54M
            ClifCfi::Offset(reg, offset) => GimliCfi::Offset(Register(reg), offset),
109
0
            ClifCfi::ValOffset(reg, offset) => GimliCfi::ValOffset(Register(reg), offset),
110
0
            ClifCfi::Register(reg1, reg2) => GimliCfi::Register(Register(reg1), Register(reg2)),
111
0
            ClifCfi::RememberState => GimliCfi::RememberState,
112
0
            ClifCfi::RestoreState => GimliCfi::RestoreState,
113
0
            ClifCfi::ArgsSize(size) => GimliCfi::ArgsSize(size),
114
0
            ClifCfi::Aarch64SetPointerAuth { return_addresses } => {
115
                // To enable pointer authentication for return addresses in dwarf directives, we
116
                // use a small dwarf expression that sets the value of the pseudo-register
117
                // RA_SIGN_STATE (RA stands for return address) to 0 or 1. This behavior is
118
                // documented in
119
                // https://github.com/ARM-software/abi-aa/blob/master/aadwarf64/aadwarf64.rst#41dwarf-register-names.
120
0
                let mut expr = Expression::new();
121
0
                expr.op(if return_addresses {
122
0
                    gimli::DW_OP_lit1
123
                } else {
124
0
                    gimli::DW_OP_lit0
125
                });
126
                const RA_SIGN_STATE: Register = Register(34);
127
0
                GimliCfi::ValExpression(RA_SIGN_STATE, expr)
128
            }
129
        }
130
6.89M
    }
131
}
132
133
/// Maps UnwindInfo register to gimli's index space.
134
pub(crate) trait RegisterMapper<Reg> {
135
    /// Maps Reg.
136
    fn map(&self, reg: Reg) -> Result<Register, RegisterMappingError>;
137
    /// Gets the frame pointer register, if any.
138
0
    fn fp(&self) -> Option<Register> {
139
0
        None
140
0
    }
141
    /// Gets the link register, if any.
142
1.23M
    fn lr(&self) -> Option<Register> {
143
1.23M
        None
144
1.23M
    }
<cranelift_codegen::isa::x64::inst::unwind::systemv::RegisterMapper as cranelift_codegen::isa::unwind::systemv::RegisterMapper<cranelift_codegen::machinst::reg::Reg>>::lr
Line
Count
Source
142
1.23M
    fn lr(&self) -> Option<Register> {
143
1.23M
        None
144
1.23M
    }
Unexecuted instantiation: <cranelift_codegen::isa::s390x::inst::unwind::systemv::RegisterMapper as cranelift_codegen::isa::unwind::systemv::RegisterMapper<cranelift_codegen::machinst::reg::Reg>>::lr
145
    /// What is the offset from saved FP to saved LR?
146
0
    fn lr_offset(&self) -> Option<u32> {
147
0
        None
148
0
    }
Unexecuted instantiation: <cranelift_codegen::isa::x64::inst::unwind::systemv::RegisterMapper as cranelift_codegen::isa::unwind::systemv::RegisterMapper<cranelift_codegen::machinst::reg::Reg>>::lr_offset
Unexecuted instantiation: <cranelift_codegen::isa::s390x::inst::unwind::systemv::RegisterMapper as cranelift_codegen::isa::unwind::systemv::RegisterMapper<cranelift_codegen::machinst::reg::Reg>>::lr_offset
149
}
150
151
/// Represents unwind information for a single System V ABI function.
152
///
153
/// This representation is not ISA specific.
154
#[derive(Clone, Debug, PartialEq, Eq)]
155
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
156
pub struct UnwindInfo {
157
    instructions: Vec<(u32, CallFrameInstruction)>,
158
    len: u32,
159
}
160
161
/// Offset from the caller's SP to CFA as we define it.
162
0
pub(crate) fn caller_sp_to_cfa_offset() -> u32 {
163
    // Currently we define them to always be equal.
164
0
    0
165
0
}
166
167
1.23M
pub(crate) fn create_unwind_info_from_insts<MR: RegisterMapper<Reg>>(
168
1.23M
    insts: &[(CodeOffset, UnwindInst)],
169
1.23M
    code_len: usize,
170
1.23M
    mr: &MR,
171
1.23M
) -> CodegenResult<UnwindInfo> {
172
1.23M
    let mut instructions = vec![];
173
174
1.23M
    let mut cfa_offset = 0;
175
1.23M
    let mut clobber_offset_to_cfa = 0;
176
5.91M
    for &(instruction_offset, ref inst) in insts {
177
5.91M
        match inst {
178
            &UnwindInst::PushFrameRegs {
179
1.23M
                offset_upward_to_caller_sp,
180
            } => {
181
                // Define CFA in terms of current SP (SP changed and we haven't
182
                // set FP yet).
183
1.23M
                instructions.push((
184
1.23M
                    instruction_offset,
185
1.23M
                    CallFrameInstruction::CfaOffset(offset_upward_to_caller_sp as i32),
186
1.23M
                ));
187
                // Note that we saved the old FP value on the stack.  Use of this
188
                // operation implies that the target defines a FP register.
189
1.23M
                instructions.push((
190
1.23M
                    instruction_offset,
191
1.23M
                    CallFrameInstruction::Offset(
192
1.23M
                        mr.fp().unwrap(),
193
1.23M
                        -(offset_upward_to_caller_sp as i32),
194
1.23M
                    ),
195
1.23M
                ));
196
                // If there is a link register on this architecture, note that
197
                // we saved it as well.
198
1.23M
                if let Some(lr) = mr.lr() {
199
0
                    instructions.push((
200
0
                        instruction_offset,
201
0
                        CallFrameInstruction::Offset(
202
0
                            lr,
203
0
                            -(offset_upward_to_caller_sp as i32)
204
0
                                + mr.lr_offset().expect("LR offset not provided") as i32,
205
0
                        ),
206
0
                    ));
207
1.23M
                }
208
            }
209
            &UnwindInst::DefineNewFrame {
210
1.23M
                offset_upward_to_caller_sp,
211
1.23M
                offset_downward_to_clobbers,
212
            } => {
213
                // Define CFA in terms of FP. Note that we assume it was already
214
                // defined correctly in terms of the current SP, and FP has just
215
                // been set to the current SP, so we do not need to change the
216
                // offset, only the register.  (This is done only if the target
217
                // defines a frame pointer register.)
218
1.23M
                if let Some(fp) = mr.fp() {
219
1.23M
                    instructions.push((instruction_offset, CallFrameInstruction::CfaRegister(fp)));
220
1.23M
                }
221
                // Record initial CFA offset.  This will be used with later
222
                // StackAlloc calls if we do not have a frame pointer.
223
1.23M
                cfa_offset = offset_upward_to_caller_sp;
224
                // Record distance from CFA downward to clobber area so we can
225
                // express clobber offsets later in terms of CFA.
226
1.23M
                clobber_offset_to_cfa = offset_upward_to_caller_sp + offset_downward_to_clobbers;
227
            }
228
0
            &UnwindInst::StackAlloc { size } => {
229
                // If we do not use a frame pointer, we need to update the
230
                // CFA offset whenever the stack pointer changes.
231
0
                if mr.fp().is_none() {
232
0
                    cfa_offset += size;
233
0
                    instructions.push((
234
0
                        instruction_offset,
235
0
                        CallFrameInstruction::CfaOffset(cfa_offset as i32),
236
0
                    ));
237
0
                }
238
            }
239
            &UnwindInst::SaveReg {
240
3.43M
                clobber_offset,
241
3.43M
                reg,
242
            } => {
243
3.43M
                let reg = mr
244
3.43M
                    .map(reg.into())
245
3.43M
                    .map_err(|e| CodegenError::RegisterMappingError(e))?;
Unexecuted instantiation: cranelift_codegen::isa::unwind::systemv::create_unwind_info_from_insts::<cranelift_codegen::isa::x64::inst::unwind::systemv::RegisterMapper>::{closure#0}
Unexecuted instantiation: cranelift_codegen::isa::unwind::systemv::create_unwind_info_from_insts::<cranelift_codegen::isa::s390x::inst::unwind::systemv::RegisterMapper>::{closure#0}
Unexecuted instantiation: cranelift_codegen::isa::unwind::systemv::create_unwind_info_from_insts::<cranelift_codegen::isa::aarch64::inst::unwind::systemv::RegisterMapper>::{closure#0}
Unexecuted instantiation: cranelift_codegen::isa::unwind::systemv::create_unwind_info_from_insts::<cranelift_codegen::isa::riscv64::inst::unwind::systemv::RegisterMapper>::{closure#0}
246
3.43M
                let off = (clobber_offset as i32) - (clobber_offset_to_cfa as i32);
247
3.43M
                instructions.push((instruction_offset, CallFrameInstruction::Offset(reg, off)));
248
            }
249
            &UnwindInst::RegStackOffset {
250
0
                clobber_offset,
251
0
                reg,
252
            } => {
253
0
                let reg = mr
254
0
                    .map(reg.into())
255
0
                    .map_err(|e| CodegenError::RegisterMappingError(e))?;
Unexecuted instantiation: cranelift_codegen::isa::unwind::systemv::create_unwind_info_from_insts::<cranelift_codegen::isa::x64::inst::unwind::systemv::RegisterMapper>::{closure#1}
Unexecuted instantiation: cranelift_codegen::isa::unwind::systemv::create_unwind_info_from_insts::<cranelift_codegen::isa::s390x::inst::unwind::systemv::RegisterMapper>::{closure#1}
Unexecuted instantiation: cranelift_codegen::isa::unwind::systemv::create_unwind_info_from_insts::<cranelift_codegen::isa::aarch64::inst::unwind::systemv::RegisterMapper>::{closure#1}
Unexecuted instantiation: cranelift_codegen::isa::unwind::systemv::create_unwind_info_from_insts::<cranelift_codegen::isa::riscv64::inst::unwind::systemv::RegisterMapper>::{closure#1}
256
0
                let off = (clobber_offset as i32) - (clobber_offset_to_cfa as i32);
257
0
                instructions.push((
258
0
                    instruction_offset,
259
0
                    CallFrameInstruction::ValOffset(reg, off),
260
0
                ));
261
            }
262
0
            &UnwindInst::Aarch64SetPointerAuth { return_addresses } => {
263
0
                instructions.push((
264
0
                    instruction_offset,
265
0
                    CallFrameInstruction::Aarch64SetPointerAuth { return_addresses },
266
0
                ));
267
0
            }
268
        }
269
    }
270
271
1.23M
    Ok(UnwindInfo {
272
1.23M
        instructions,
273
1.23M
        len: code_len as u32,
274
1.23M
    })
275
1.23M
}
cranelift_codegen::isa::unwind::systemv::create_unwind_info_from_insts::<cranelift_codegen::isa::x64::inst::unwind::systemv::RegisterMapper>
Line
Count
Source
167
1.23M
pub(crate) fn create_unwind_info_from_insts<MR: RegisterMapper<Reg>>(
168
1.23M
    insts: &[(CodeOffset, UnwindInst)],
169
1.23M
    code_len: usize,
170
1.23M
    mr: &MR,
171
1.23M
) -> CodegenResult<UnwindInfo> {
172
1.23M
    let mut instructions = vec![];
173
174
1.23M
    let mut cfa_offset = 0;
175
1.23M
    let mut clobber_offset_to_cfa = 0;
176
5.91M
    for &(instruction_offset, ref inst) in insts {
177
5.91M
        match inst {
178
            &UnwindInst::PushFrameRegs {
179
1.23M
                offset_upward_to_caller_sp,
180
            } => {
181
                // Define CFA in terms of current SP (SP changed and we haven't
182
                // set FP yet).
183
1.23M
                instructions.push((
184
1.23M
                    instruction_offset,
185
1.23M
                    CallFrameInstruction::CfaOffset(offset_upward_to_caller_sp as i32),
186
1.23M
                ));
187
                // Note that we saved the old FP value on the stack.  Use of this
188
                // operation implies that the target defines a FP register.
189
1.23M
                instructions.push((
190
1.23M
                    instruction_offset,
191
1.23M
                    CallFrameInstruction::Offset(
192
1.23M
                        mr.fp().unwrap(),
193
1.23M
                        -(offset_upward_to_caller_sp as i32),
194
1.23M
                    ),
195
1.23M
                ));
196
                // If there is a link register on this architecture, note that
197
                // we saved it as well.
198
1.23M
                if let Some(lr) = mr.lr() {
199
0
                    instructions.push((
200
0
                        instruction_offset,
201
0
                        CallFrameInstruction::Offset(
202
0
                            lr,
203
0
                            -(offset_upward_to_caller_sp as i32)
204
0
                                + mr.lr_offset().expect("LR offset not provided") as i32,
205
0
                        ),
206
0
                    ));
207
1.23M
                }
208
            }
209
            &UnwindInst::DefineNewFrame {
210
1.23M
                offset_upward_to_caller_sp,
211
1.23M
                offset_downward_to_clobbers,
212
            } => {
213
                // Define CFA in terms of FP. Note that we assume it was already
214
                // defined correctly in terms of the current SP, and FP has just
215
                // been set to the current SP, so we do not need to change the
216
                // offset, only the register.  (This is done only if the target
217
                // defines a frame pointer register.)
218
1.23M
                if let Some(fp) = mr.fp() {
219
1.23M
                    instructions.push((instruction_offset, CallFrameInstruction::CfaRegister(fp)));
220
1.23M
                }
221
                // Record initial CFA offset.  This will be used with later
222
                // StackAlloc calls if we do not have a frame pointer.
223
1.23M
                cfa_offset = offset_upward_to_caller_sp;
224
                // Record distance from CFA downward to clobber area so we can
225
                // express clobber offsets later in terms of CFA.
226
1.23M
                clobber_offset_to_cfa = offset_upward_to_caller_sp + offset_downward_to_clobbers;
227
            }
228
0
            &UnwindInst::StackAlloc { size } => {
229
                // If we do not use a frame pointer, we need to update the
230
                // CFA offset whenever the stack pointer changes.
231
0
                if mr.fp().is_none() {
232
0
                    cfa_offset += size;
233
0
                    instructions.push((
234
0
                        instruction_offset,
235
0
                        CallFrameInstruction::CfaOffset(cfa_offset as i32),
236
0
                    ));
237
0
                }
238
            }
239
            &UnwindInst::SaveReg {
240
3.43M
                clobber_offset,
241
3.43M
                reg,
242
            } => {
243
3.43M
                let reg = mr
244
3.43M
                    .map(reg.into())
245
3.43M
                    .map_err(|e| CodegenError::RegisterMappingError(e))?;
246
3.43M
                let off = (clobber_offset as i32) - (clobber_offset_to_cfa as i32);
247
3.43M
                instructions.push((instruction_offset, CallFrameInstruction::Offset(reg, off)));
248
            }
249
            &UnwindInst::RegStackOffset {
250
0
                clobber_offset,
251
0
                reg,
252
            } => {
253
0
                let reg = mr
254
0
                    .map(reg.into())
255
0
                    .map_err(|e| CodegenError::RegisterMappingError(e))?;
256
0
                let off = (clobber_offset as i32) - (clobber_offset_to_cfa as i32);
257
0
                instructions.push((
258
0
                    instruction_offset,
259
0
                    CallFrameInstruction::ValOffset(reg, off),
260
0
                ));
261
            }
262
0
            &UnwindInst::Aarch64SetPointerAuth { return_addresses } => {
263
0
                instructions.push((
264
0
                    instruction_offset,
265
0
                    CallFrameInstruction::Aarch64SetPointerAuth { return_addresses },
266
0
                ));
267
0
            }
268
        }
269
    }
270
271
1.23M
    Ok(UnwindInfo {
272
1.23M
        instructions,
273
1.23M
        len: code_len as u32,
274
1.23M
    })
275
1.23M
}
Unexecuted instantiation: cranelift_codegen::isa::unwind::systemv::create_unwind_info_from_insts::<cranelift_codegen::isa::s390x::inst::unwind::systemv::RegisterMapper>
Unexecuted instantiation: cranelift_codegen::isa::unwind::systemv::create_unwind_info_from_insts::<cranelift_codegen::isa::aarch64::inst::unwind::systemv::RegisterMapper>
Unexecuted instantiation: cranelift_codegen::isa::unwind::systemv::create_unwind_info_from_insts::<cranelift_codegen::isa::riscv64::inst::unwind::systemv::RegisterMapper>
276
277
impl UnwindInfo {
278
    /// Converts the unwind information into a `FrameDescriptionEntry`.
279
1.17M
    pub fn to_fde(&self, address: Address) -> gimli::write::FrameDescriptionEntry {
280
1.17M
        let mut fde = FrameDescriptionEntry::new(address, self.len);
281
282
6.89M
        for (offset, inst) in &self.instructions {
283
6.89M
            fde.add_instruction(*offset, inst.clone().into());
284
6.89M
        }
285
286
1.17M
        fde
287
1.17M
    }
288
}