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