1 //! Unwind information for System V ABI (x86-64).
2
3 use crate::ir::{Function, Inst, InstructionData, Opcode, Value};
4 use crate::isa::{
5 unwind::systemv::{CallFrameInstruction, RegisterMappingError, UnwindInfo},
6 x86::registers::RU,
7 CallConv, RegUnit, TargetIsa,
8 };
9 use crate::result::{CodegenError, CodegenResult};
10 use alloc::vec::Vec;
11 use gimli::{write::CommonInformationEntry, Encoding, Format, Register, X86_64};
12
13 /// Creates a new x86-64 common information entry (CIE).
create_cie() -> CommonInformationEntry14 pub fn create_cie() -> CommonInformationEntry {
15 use gimli::write::CallFrameInstruction;
16
17 let mut entry = CommonInformationEntry::new(
18 Encoding {
19 address_size: 8,
20 format: Format::Dwarf32,
21 version: 1,
22 },
23 1, // Code alignment factor
24 -8, // Data alignment factor
25 X86_64::RA,
26 );
27
28 // Every frame will start with the call frame address (CFA) at RSP+8
29 // It is +8 to account for the push of the return address by the call instruction
30 entry.add_instruction(CallFrameInstruction::Cfa(X86_64::RSP, 8));
31
32 // Every frame will start with the return address at RSP (CFA-8 = RSP+8-8 = RSP)
33 entry.add_instruction(CallFrameInstruction::Offset(X86_64::RA, -8));
34
35 entry
36 }
37
38 /// Map Cranelift registers to their corresponding Gimli registers.
map_reg(isa: &dyn TargetIsa, reg: RegUnit) -> Result<Register, RegisterMappingError>39 pub fn map_reg(isa: &dyn TargetIsa, reg: RegUnit) -> Result<Register, RegisterMappingError> {
40 if isa.name() != "x86" || isa.pointer_bits() != 64 {
41 return Err(RegisterMappingError::UnsupportedArchitecture);
42 }
43
44 // Mapping from https://github.com/bytecodealliance/cranelift/pull/902 by @iximeow
45 const X86_GP_REG_MAP: [gimli::Register; 16] = [
46 X86_64::RAX,
47 X86_64::RCX,
48 X86_64::RDX,
49 X86_64::RBX,
50 X86_64::RSP,
51 X86_64::RBP,
52 X86_64::RSI,
53 X86_64::RDI,
54 X86_64::R8,
55 X86_64::R9,
56 X86_64::R10,
57 X86_64::R11,
58 X86_64::R12,
59 X86_64::R13,
60 X86_64::R14,
61 X86_64::R15,
62 ];
63 const X86_XMM_REG_MAP: [gimli::Register; 16] = [
64 X86_64::XMM0,
65 X86_64::XMM1,
66 X86_64::XMM2,
67 X86_64::XMM3,
68 X86_64::XMM4,
69 X86_64::XMM5,
70 X86_64::XMM6,
71 X86_64::XMM7,
72 X86_64::XMM8,
73 X86_64::XMM9,
74 X86_64::XMM10,
75 X86_64::XMM11,
76 X86_64::XMM12,
77 X86_64::XMM13,
78 X86_64::XMM14,
79 X86_64::XMM15,
80 ];
81
82 let reg_info = isa.register_info();
83 let bank = reg_info
84 .bank_containing_regunit(reg)
85 .ok_or_else(|| RegisterMappingError::MissingBank)?;
86 match bank.name {
87 "IntRegs" => {
88 // x86 GP registers have a weird mapping to DWARF registers, so we use a
89 // lookup table.
90 Ok(X86_GP_REG_MAP[(reg - bank.first_unit) as usize])
91 }
92 "FloatRegs" => Ok(X86_XMM_REG_MAP[(reg - bank.first_unit) as usize]),
93 _ => Err(RegisterMappingError::UnsupportedRegisterBank(bank.name)),
94 }
95 }
96
97 struct InstructionBuilder<'a> {
98 func: &'a Function,
99 isa: &'a dyn TargetIsa,
100 cfa_offset: i32,
101 frame_register: Option<RegUnit>,
102 instructions: Vec<(u32, CallFrameInstruction)>,
103 stack_size: Option<i32>,
104 epilogue_pop_offsets: Vec<u32>,
105 }
106
107 impl<'a> InstructionBuilder<'a> {
new(func: &'a Function, isa: &'a dyn TargetIsa, frame_register: Option<RegUnit>) -> Self108 fn new(func: &'a Function, isa: &'a dyn TargetIsa, frame_register: Option<RegUnit>) -> Self {
109 Self {
110 func,
111 isa,
112 cfa_offset: 8, // CFA offset starts at 8 to account to return address on stack
113 frame_register,
114 instructions: Vec::new(),
115 stack_size: None,
116 epilogue_pop_offsets: Vec::new(),
117 }
118 }
119
push_reg(&mut self, offset: u32, arg: Value) -> Result<(), RegisterMappingError>120 fn push_reg(&mut self, offset: u32, arg: Value) -> Result<(), RegisterMappingError> {
121 self.cfa_offset += 8;
122
123 let reg = self.func.locations[arg].unwrap_reg();
124
125 // Update the CFA if this is the save of the frame pointer register or if a frame pointer isn't being used
126 // When using a frame pointer, we only need to update the CFA to account for the push of the frame pointer itself
127 if match self.frame_register {
128 Some(fp) => reg == fp,
129 None => true,
130 } {
131 self.instructions
132 .push((offset, CallFrameInstruction::CfaOffset(self.cfa_offset)));
133 }
134
135 // Pushes in the prologue are register saves, so record an offset of the save
136 self.instructions.push((
137 offset,
138 CallFrameInstruction::Offset(map_reg(self.isa, reg)?.0, -self.cfa_offset),
139 ));
140
141 Ok(())
142 }
143
adjust_sp_down(&mut self, offset: u32)144 fn adjust_sp_down(&mut self, offset: u32) {
145 // Don't adjust the CFA if we're using a frame pointer
146 if self.frame_register.is_some() {
147 return;
148 }
149
150 self.cfa_offset += self
151 .stack_size
152 .expect("expected a previous stack size instruction");
153 self.instructions
154 .push((offset, CallFrameInstruction::CfaOffset(self.cfa_offset)));
155 }
156
adjust_sp_down_imm(&mut self, offset: u32, imm: i64)157 fn adjust_sp_down_imm(&mut self, offset: u32, imm: i64) {
158 assert!(imm <= core::u32::MAX as i64);
159
160 // Don't adjust the CFA if we're using a frame pointer
161 if self.frame_register.is_some() {
162 return;
163 }
164
165 self.cfa_offset += imm as i32;
166 self.instructions
167 .push((offset, CallFrameInstruction::CfaOffset(self.cfa_offset)));
168 }
169
adjust_sp_up_imm(&mut self, offset: u32, imm: i64)170 fn adjust_sp_up_imm(&mut self, offset: u32, imm: i64) {
171 assert!(imm <= core::u32::MAX as i64);
172
173 // Don't adjust the CFA if we're using a frame pointer
174 if self.frame_register.is_some() {
175 return;
176 }
177
178 self.cfa_offset -= imm as i32;
179 self.instructions
180 .push((offset, CallFrameInstruction::CfaOffset(self.cfa_offset)));
181 }
182
move_reg( &mut self, offset: u32, src: RegUnit, dst: RegUnit, ) -> Result<(), RegisterMappingError>183 fn move_reg(
184 &mut self,
185 offset: u32,
186 src: RegUnit,
187 dst: RegUnit,
188 ) -> Result<(), RegisterMappingError> {
189 if let Some(fp) = self.frame_register {
190 // Check for change in CFA register (RSP is always the starting CFA)
191 if src == (RU::rsp as RegUnit) && dst == fp {
192 self.instructions.push((
193 offset,
194 CallFrameInstruction::CfaRegister(map_reg(self.isa, dst)?.0),
195 ));
196 }
197 }
198
199 Ok(())
200 }
201
prologue_imm_const(&mut self, imm: i64)202 fn prologue_imm_const(&mut self, imm: i64) {
203 assert!(imm <= core::u32::MAX as i64);
204 assert!(self.stack_size.is_none());
205
206 // This instruction should only appear in a prologue to pass an
207 // argument of the stack size to a stack check function.
208 // Record the stack size so we know what it is when we encounter the adjustment
209 // instruction (which will adjust via the register assigned to this instruction).
210 self.stack_size = Some(imm as i32);
211 }
212
ret(&mut self, inst: Inst) -> Result<(), RegisterMappingError>213 fn ret(&mut self, inst: Inst) -> Result<(), RegisterMappingError> {
214 let args = self.func.dfg.inst_args(inst);
215
216 for (i, arg) in args.iter().rev().enumerate() {
217 // Only walk back the args for the pop instructions encountered
218 if i >= self.epilogue_pop_offsets.len() {
219 break;
220 }
221
222 self.cfa_offset -= 8;
223 let reg = self.func.locations[*arg].unwrap_reg();
224
225 // Update the CFA if this is the restore of the frame pointer register or if a frame pointer isn't being used
226 match self.frame_register {
227 Some(fp) => {
228 if reg == fp {
229 self.instructions.push((
230 self.epilogue_pop_offsets[i],
231 CallFrameInstruction::Cfa(
232 map_reg(self.isa, RU::rsp as RegUnit)?.0,
233 self.cfa_offset,
234 ),
235 ));
236 }
237 }
238 None => {
239 self.instructions.push((
240 self.epilogue_pop_offsets[i],
241 CallFrameInstruction::CfaOffset(self.cfa_offset),
242 ));
243
244 // Pops in the epilogue are register restores, so record a "same value" for the register
245 // This isn't necessary when using a frame pointer as the CFA doesn't change for CSR restores
246 self.instructions.push((
247 self.epilogue_pop_offsets[i],
248 CallFrameInstruction::SameValue(map_reg(self.isa, reg)?.0),
249 ));
250 }
251 };
252 }
253
254 self.epilogue_pop_offsets.clear();
255
256 Ok(())
257 }
258
insert_pop_offset(&mut self, offset: u32)259 fn insert_pop_offset(&mut self, offset: u32) {
260 self.epilogue_pop_offsets.push(offset);
261 }
262
remember_state(&mut self, offset: u32)263 fn remember_state(&mut self, offset: u32) {
264 self.instructions
265 .push((offset, CallFrameInstruction::RememberState));
266 }
267
restore_state(&mut self, offset: u32)268 fn restore_state(&mut self, offset: u32) {
269 self.instructions
270 .push((offset, CallFrameInstruction::RestoreState));
271 }
272
is_prologue_end(&self, inst: Inst) -> bool273 fn is_prologue_end(&self, inst: Inst) -> bool {
274 self.func.prologue_end == Some(inst)
275 }
276
is_epilogue_start(&self, inst: Inst) -> bool277 fn is_epilogue_start(&self, inst: Inst) -> bool {
278 self.func.epilogues_start.contains(&inst)
279 }
280 }
281
create_unwind_info( func: &Function, isa: &dyn TargetIsa, frame_register: Option<RegUnit>, ) -> CodegenResult<Option<UnwindInfo>>282 pub(crate) fn create_unwind_info(
283 func: &Function,
284 isa: &dyn TargetIsa,
285 frame_register: Option<RegUnit>,
286 ) -> CodegenResult<Option<UnwindInfo>> {
287 // Only System V-like calling conventions are supported
288 match func.signature.call_conv {
289 CallConv::Fast | CallConv::Cold | CallConv::SystemV => {}
290 _ => return Ok(None),
291 }
292
293 if func.prologue_end.is_none() || isa.name() != "x86" || isa.pointer_bits() != 64 {
294 return Ok(None);
295 }
296
297 let mut builder = InstructionBuilder::new(func, isa, frame_register);
298 let mut in_prologue = true;
299 let mut in_epilogue = false;
300 let mut len = 0;
301
302 let mut blocks = func.layout.blocks().collect::<Vec<_>>();
303 blocks.sort_by_key(|b| func.offsets[*b]);
304
305 for (i, block) in blocks.iter().enumerate() {
306 for (offset, inst, size) in func.inst_offsets(*block, &isa.encoding_info()) {
307 let offset = offset + size;
308 assert!(len <= offset);
309 len = offset;
310
311 let is_last_block = i == blocks.len() - 1;
312
313 if in_prologue {
314 // Check for prologue end (inclusive)
315 in_prologue = !builder.is_prologue_end(inst);
316 } else if !in_epilogue && builder.is_epilogue_start(inst) {
317 // Now in an epilogue, emit a remember state instruction if not last block
318 in_epilogue = true;
319
320 if !is_last_block {
321 builder.remember_state(offset);
322 }
323 } else if !in_epilogue {
324 // Ignore normal instructions
325 continue;
326 }
327
328 match builder.func.dfg[inst] {
329 InstructionData::Unary { opcode, arg } => match opcode {
330 Opcode::X86Push => {
331 builder
332 .push_reg(offset, arg)
333 .map_err(CodegenError::RegisterMappingError)?;
334 }
335 Opcode::AdjustSpDown => {
336 builder.adjust_sp_down(offset);
337 }
338 _ => {}
339 },
340 InstructionData::CopySpecial { src, dst, .. } => {
341 builder
342 .move_reg(offset, src, dst)
343 .map_err(CodegenError::RegisterMappingError)?;
344 }
345 InstructionData::NullAry { opcode } => match opcode {
346 Opcode::X86Pop => {
347 builder.insert_pop_offset(offset);
348 }
349 _ => {}
350 },
351 InstructionData::UnaryImm { opcode, imm } => match opcode {
352 Opcode::Iconst => {
353 builder.prologue_imm_const(imm.into());
354 }
355 Opcode::AdjustSpDownImm => {
356 builder.adjust_sp_down_imm(offset, imm.into());
357 }
358 Opcode::AdjustSpUpImm => {
359 builder.adjust_sp_up_imm(offset, imm.into());
360 }
361 _ => {}
362 },
363 InstructionData::MultiAry { opcode, .. } => match opcode {
364 Opcode::Return => {
365 builder
366 .ret(inst)
367 .map_err(CodegenError::RegisterMappingError)?;
368
369 if !is_last_block {
370 builder.restore_state(offset);
371 }
372
373 in_epilogue = false;
374 }
375 _ => {}
376 },
377 _ => {}
378 };
379 }
380 }
381
382 Ok(Some(UnwindInfo::new(builder.instructions, len)))
383 }
384
385 #[cfg(test)]
386 mod tests {
387 use super::*;
388 use crate::cursor::{Cursor, FuncCursor};
389 use crate::ir::{
390 types, AbiParam, ExternalName, InstBuilder, Signature, StackSlotData, StackSlotKind,
391 };
392 use crate::isa::{lookup, CallConv};
393 use crate::settings::{builder, Flags};
394 use crate::Context;
395 use gimli::write::Address;
396 use std::str::FromStr;
397 use target_lexicon::triple;
398
399 #[test]
test_simple_func()400 fn test_simple_func() {
401 let isa = lookup(triple!("x86_64"))
402 .expect("expect x86 ISA")
403 .finish(Flags::new(builder()));
404
405 let mut context = Context::for_function(create_function(
406 CallConv::SystemV,
407 Some(StackSlotData::new(StackSlotKind::ExplicitSlot, 64)),
408 ));
409
410 context.compile(&*isa).expect("expected compilation");
411
412 let fde = match isa
413 .create_unwind_info(&context.func)
414 .expect("can create unwind info")
415 {
416 Some(crate::isa::unwind::UnwindInfo::SystemV(info)) => {
417 info.to_fde(Address::Constant(1234))
418 }
419 _ => panic!("expected unwind information"),
420 };
421
422 assert_eq!(format!("{:?}", fde), "FrameDescriptionEntry { address: Constant(1234), length: 16, lsda: None, instructions: [(2, CfaOffset(16)), (2, Offset(Register(6), -16)), (5, CfaRegister(Register(6))), (15, Cfa(Register(7), 8))] }");
423 }
424
create_function(call_conv: CallConv, stack_slot: Option<StackSlotData>) -> Function425 fn create_function(call_conv: CallConv, stack_slot: Option<StackSlotData>) -> Function {
426 let mut func =
427 Function::with_name_signature(ExternalName::user(0, 0), Signature::new(call_conv));
428
429 let block0 = func.dfg.make_block();
430 let mut pos = FuncCursor::new(&mut func);
431 pos.insert_block(block0);
432 pos.ins().return_(&[]);
433
434 if let Some(stack_slot) = stack_slot {
435 func.stack_slots.push(stack_slot);
436 }
437
438 func
439 }
440
441 #[test]
test_multi_return_func()442 fn test_multi_return_func() {
443 let isa = lookup(triple!("x86_64"))
444 .expect("expect x86 ISA")
445 .finish(Flags::new(builder()));
446
447 let mut context = Context::for_function(create_multi_return_function(CallConv::SystemV));
448
449 context.compile(&*isa).expect("expected compilation");
450
451 let fde = match isa
452 .create_unwind_info(&context.func)
453 .expect("can create unwind info")
454 {
455 Some(crate::isa::unwind::UnwindInfo::SystemV(info)) => {
456 info.to_fde(Address::Constant(4321))
457 }
458 _ => panic!("expected unwind information"),
459 };
460
461 assert_eq!(format!("{:?}", fde), "FrameDescriptionEntry { address: Constant(4321), length: 16, lsda: None, instructions: [(2, CfaOffset(16)), (2, Offset(Register(6), -16)), (5, CfaRegister(Register(6))), (12, RememberState), (12, Cfa(Register(7), 8)), (13, RestoreState), (15, Cfa(Register(7), 0))] }");
462 }
463
create_multi_return_function(call_conv: CallConv) -> Function464 fn create_multi_return_function(call_conv: CallConv) -> Function {
465 let mut sig = Signature::new(call_conv);
466 sig.params.push(AbiParam::new(types::I32));
467 let mut func = Function::with_name_signature(ExternalName::user(0, 0), sig);
468
469 let block0 = func.dfg.make_block();
470 let v0 = func.dfg.append_block_param(block0, types::I32);
471 let block1 = func.dfg.make_block();
472 let block2 = func.dfg.make_block();
473
474 let mut pos = FuncCursor::new(&mut func);
475 pos.insert_block(block0);
476 pos.ins().brnz(v0, block2, &[]);
477 pos.ins().jump(block1, &[]);
478
479 pos.insert_block(block1);
480 pos.ins().return_(&[]);
481
482 pos.insert_block(block2);
483 pos.ins().return_(&[]);
484
485 func
486 }
487 }
488