/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 | | } |