Files
wasmtime/cranelift/codegen/src/isa/x86/fde.rs
Andrew Brown 6fd0451bc3 Add TargetIsa::map_dwarf_register; fixes #1471
This exposes the functionality of `fde::map_reg` on the `TargetIsa` trait, avoiding compilation errors on architectures where register mapping is not yet supported. The change is conditially compiled under the `unwind` feature.
2020-04-09 09:45:20 -07:00

442 lines
14 KiB
Rust

//! Support for FDE data generation.
use crate::binemit::{FrameUnwindOffset, FrameUnwindSink, Reloc};
use crate::ir::{FrameLayoutChange, Function};
use crate::isa::fde::RegisterMappingError;
use crate::isa::{CallConv, RegUnit, TargetIsa};
use alloc::vec::Vec;
use core::convert::TryInto;
use gimli::write::{
Address, CallFrameInstruction, CommonInformationEntry, EhFrame, EndianVec,
FrameDescriptionEntry, FrameTable, Result, Writer,
};
use gimli::{Encoding, Format, LittleEndian, Register, X86_64};
pub type FDERelocEntry = (FrameUnwindOffset, Reloc);
const FUNCTION_ENTRY_ADDRESS: Address = Address::Symbol {
symbol: 0,
addend: 0,
};
#[derive(Clone)]
struct FDEWriter {
vec: EndianVec<LittleEndian>,
relocs: Vec<FDERelocEntry>,
}
impl FDEWriter {
fn new() -> Self {
Self {
vec: EndianVec::new(LittleEndian),
relocs: Vec::new(),
}
}
fn into_vec_and_relocs(self) -> (Vec<u8>, Vec<FDERelocEntry>) {
(self.vec.into_vec(), self.relocs)
}
}
impl Writer for FDEWriter {
type Endian = LittleEndian;
fn endian(&self) -> Self::Endian {
LittleEndian
}
fn len(&self) -> usize {
self.vec.len()
}
fn write(&mut self, bytes: &[u8]) -> Result<()> {
self.vec.write(bytes)
}
fn write_at(&mut self, offset: usize, bytes: &[u8]) -> Result<()> {
self.vec.write_at(offset, bytes)
}
fn write_address(&mut self, address: Address, size: u8) -> Result<()> {
match address {
Address::Constant(_) => self.vec.write_address(address, size),
Address::Symbol { .. } => {
assert_eq!(address, FUNCTION_ENTRY_ADDRESS);
let rt = match size {
4 => Reloc::Abs4,
8 => Reloc::Abs8,
_ => {
panic!("Unexpected address size at FDEWriter::write_address");
}
};
self.relocs.push((self.vec.len().try_into().unwrap(), rt));
self.vec.write_udata(0, size)
}
}
}
}
fn return_address_reg(isa: &dyn TargetIsa) -> Register {
assert!(isa.name() == "x86" && isa.pointer_bits() == 64);
X86_64::RA
}
/// Map Cranelift registers to their corresponding Gimli registers.
pub fn map_reg(
isa: &dyn TargetIsa,
reg: RegUnit,
) -> core::result::Result<Register, RegisterMappingError> {
if isa.name() != "x86" || isa.pointer_bits() != 64 {
return Err(RegisterMappingError::UnsupportedArchitecture);
}
// Mapping from https://github.com/bytecodealliance/cranelift/pull/902 by @iximeow
const X86_GP_REG_MAP: [gimli::Register; 16] = [
X86_64::RAX,
X86_64::RCX,
X86_64::RDX,
X86_64::RBX,
X86_64::RSP,
X86_64::RBP,
X86_64::RSI,
X86_64::RDI,
X86_64::R8,
X86_64::R9,
X86_64::R10,
X86_64::R11,
X86_64::R12,
X86_64::R13,
X86_64::R14,
X86_64::R15,
];
const X86_XMM_REG_MAP: [gimli::Register; 16] = [
X86_64::XMM0,
X86_64::XMM1,
X86_64::XMM2,
X86_64::XMM3,
X86_64::XMM4,
X86_64::XMM5,
X86_64::XMM6,
X86_64::XMM7,
X86_64::XMM8,
X86_64::XMM9,
X86_64::XMM10,
X86_64::XMM11,
X86_64::XMM12,
X86_64::XMM13,
X86_64::XMM14,
X86_64::XMM15,
];
let reg_info = isa.register_info();
let bank = reg_info
.bank_containing_regunit(reg)
.ok_or_else(|| RegisterMappingError::MissingBank)?;
match bank.name {
"IntRegs" => {
// x86 GP registers have a weird mapping to DWARF registers, so we use a
// lookup table.
Ok(X86_GP_REG_MAP[(reg - bank.first_unit) as usize])
}
"FloatRegs" => Ok(X86_XMM_REG_MAP[(reg - bank.first_unit) as usize]),
_ => Err(RegisterMappingError::UnsupportedRegisterBank(bank.name)),
}
}
fn to_cfi(
isa: &dyn TargetIsa,
change: &FrameLayoutChange,
cfa_def_reg: &mut Register,
cfa_def_offset: &mut i32,
) -> Option<CallFrameInstruction> {
Some(match change {
FrameLayoutChange::CallFrameAddressAt { reg, offset } => {
let mapped = map_reg(isa, *reg).expect("a register mapping from cranelift to gimli");
let offset = (*offset) as i32;
if mapped != *cfa_def_reg && offset != *cfa_def_offset {
*cfa_def_reg = mapped;
*cfa_def_offset = offset;
CallFrameInstruction::Cfa(mapped, offset)
} else if offset != *cfa_def_offset {
*cfa_def_offset = offset;
CallFrameInstruction::CfaOffset(offset)
} else if mapped != *cfa_def_reg {
*cfa_def_reg = mapped;
CallFrameInstruction::CfaRegister(mapped)
} else {
return None;
}
}
FrameLayoutChange::RegAt { reg, cfa_offset } => {
assert!(cfa_offset % -8 == 0);
let cfa_offset = *cfa_offset as i32;
let mapped = map_reg(isa, *reg).expect("a register mapping from cranelift to gimli");
CallFrameInstruction::Offset(mapped, cfa_offset)
}
FrameLayoutChange::ReturnAddressAt { cfa_offset } => {
assert!(cfa_offset % -8 == 0);
let cfa_offset = *cfa_offset as i32;
CallFrameInstruction::Offset(X86_64::RA, cfa_offset)
}
FrameLayoutChange::Preserve => CallFrameInstruction::RememberState,
FrameLayoutChange::Restore => CallFrameInstruction::RestoreState,
})
}
/// Creates FDE structure from FrameLayout.
pub fn emit_fde(func: &Function, isa: &dyn TargetIsa, sink: &mut dyn FrameUnwindSink) {
assert!(isa.name() == "x86");
// Expecting function with System V prologue
assert!(
func.signature.call_conv == CallConv::Fast
|| func.signature.call_conv == CallConv::Cold
|| func.signature.call_conv == CallConv::SystemV
);
assert!(func.frame_layout.is_some(), "expected func.frame_layout");
let frame_layout = func.frame_layout.as_ref().unwrap();
let mut blocks = func.layout.blocks().collect::<Vec<_>>();
blocks.sort_by_key(|block| func.offsets[*block]); // Ensure inst offsets always increase
let encinfo = isa.encoding_info();
let mut last_offset = 0;
let mut changes = Vec::new();
for block in blocks {
for (offset, inst, size) in func.inst_offsets(block, &encinfo) {
let address_offset = (offset + size) as usize;
assert!(last_offset <= address_offset);
if let Some(cmds) = frame_layout.instructions.get(&inst) {
for cmd in cmds.iter() {
changes.push((address_offset, *cmd));
}
}
last_offset = address_offset;
}
}
let len = last_offset as u32;
let word_size = isa.pointer_bytes() as i32;
let encoding = Encoding {
format: Format::Dwarf32,
version: 1,
address_size: word_size as u8,
};
let mut frames = FrameTable::default();
let mut cfa_def_reg = return_address_reg(isa);
let mut cfa_def_offset = 0i32;
let mut cie = CommonInformationEntry::new(
encoding,
/* code_alignment_factor = */ 1,
/* data_alignment_factor = */ -word_size as i8,
return_address_reg(isa),
);
for ch in frame_layout.initial.iter() {
if let Some(cfi) = to_cfi(isa, ch, &mut cfa_def_reg, &mut cfa_def_offset) {
cie.add_instruction(cfi);
}
}
let cie_id = frames.add_cie(cie);
let mut fde = FrameDescriptionEntry::new(FUNCTION_ENTRY_ADDRESS, len);
for (addr, ch) in changes.iter() {
if let Some(cfi) = to_cfi(isa, ch, &mut cfa_def_reg, &mut cfa_def_offset) {
fde.add_instruction((*addr) as u32, cfi);
}
}
frames.add_fde(cie_id, fde);
let mut eh_frame = EhFrame::from(FDEWriter::new());
frames.write_eh_frame(&mut eh_frame).unwrap();
let (bytes, relocs) = eh_frame.clone().into_vec_and_relocs();
let unwind_start = sink.len();
sink.bytes(&bytes);
for (off, r) in relocs {
sink.reloc(r, off + unwind_start);
}
let cie_len = u32::from_le_bytes(bytes.as_slice()[..4].try_into().unwrap());
let fde_offset = cie_len as usize + 4;
sink.set_entry_offset(unwind_start + fde_offset);
// Need 0 marker for GCC unwind to end FDE "list".
sink.bytes(&[0, 0, 0, 0]);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::binemit::{FrameUnwindOffset, Reloc};
use crate::cursor::{Cursor, FuncCursor};
use crate::ir::{
types, AbiParam, ExternalName, InstBuilder, Signature, StackSlotData, StackSlotKind,
TrapCode,
};
use crate::isa::{lookup, CallConv};
use crate::settings::{builder, Flags};
use crate::Context;
use std::str::FromStr;
use target_lexicon::triple;
struct SimpleUnwindSink(pub Vec<u8>, pub usize, pub Vec<(Reloc, usize)>);
impl FrameUnwindSink for SimpleUnwindSink {
fn len(&self) -> FrameUnwindOffset {
self.0.len()
}
fn bytes(&mut self, b: &[u8]) {
self.0.extend_from_slice(b);
}
fn reloc(&mut self, r: Reloc, off: FrameUnwindOffset) {
self.2.push((r, off));
}
fn set_entry_offset(&mut self, off: FrameUnwindOffset) {
self.1 = off;
}
}
#[test]
fn test_simple_func() {
let isa = lookup(triple!("x86_64"))
.expect("expect x86 ISA")
.finish(Flags::new(builder()));
let mut context = Context::for_function(create_function(
CallConv::SystemV,
Some(StackSlotData::new(StackSlotKind::ExplicitSlot, 64)),
));
context.func.collect_frame_layout_info();
context.compile(&*isa).expect("expected compilation");
let mut sink = SimpleUnwindSink(Vec::new(), 0, Vec::new());
emit_fde(&context.func, &*isa, &mut sink);
assert_eq!(
sink.0,
vec![
20, 0, 0, 0, // CIE len
0, 0, 0, 0, // CIE marker
1, // version
0, // augmentation string
1, // code aligment = 1
120, // data alignment = -8
16, // RA = r16
0x0c, 0x07, 0x08, // DW_CFA_def_cfa r7, 8
0x90, 0x01, // DW_CFA_offset r16, -8 * 1
0, 0, 0, 0, 0, 0, // padding
36, 0, 0, 0, // FDE len
28, 0, 0, 0, // CIE offset
0, 0, 0, 0, 0, 0, 0, 0, // addr reloc
16, 0, 0, 0, 0, 0, 0, 0, // function length
0x42, // DW_CFA_advance_loc 2
0x0e, 0x10, // DW_CFA_def_cfa_offset 16
0x86, 0x02, // DW_CFA_offset r6, -8 * 2
0x43, // DW_CFA_advance_loc 3
0x0d, 0x06, // DW_CFA_def_cfa_register
0x4a, // DW_CFA_advance_loc 10
0x0c, 0x07, 0x08, // DW_CFA_def_cfa r7, 8
0, 0, 0, 0, // padding
0, 0, 0, 0, // End of FDEs
]
);
assert_eq!(sink.1, 24);
assert_eq!(sink.2.len(), 1);
}
fn create_function(call_conv: CallConv, stack_slot: Option<StackSlotData>) -> Function {
let mut func =
Function::with_name_signature(ExternalName::user(0, 0), Signature::new(call_conv));
let block0 = func.dfg.make_block();
let mut pos = FuncCursor::new(&mut func);
pos.insert_block(block0);
pos.ins().return_(&[]);
if let Some(stack_slot) = stack_slot {
func.stack_slots.push(stack_slot);
}
func
}
#[test]
fn test_multi_return_func() {
let isa = lookup(triple!("x86_64"))
.expect("expect x86 ISA")
.finish(Flags::new(builder()));
let mut context = Context::for_function(create_multi_return_function(CallConv::SystemV));
context.func.collect_frame_layout_info();
context.compile(&*isa).expect("expected compilation");
let mut sink = SimpleUnwindSink(Vec::new(), 0, Vec::new());
emit_fde(&context.func, &*isa, &mut sink);
assert_eq!(
sink.0,
vec![
20, 0, 0, 0, // CIE len
0, 0, 0, 0, // CIE marker
1, // version
0, // augmentation string
1, // code aligment = 1
120, // data alignment = -8
16, // RA = r16
0x0c, 0x07, 0x08, // DW_CFA_def_cfa r7, 8
0x90, 0x01, // DW_CFA_offset r16, -8 * 1
0, 0, 0, 0, 0, 0, // padding
36, 0, 0, 0, // FDE len
28, 0, 0, 0, // CIE offset
0, 0, 0, 0, 0, 0, 0, 0, // addr reloc
15, 0, 0, 0, 0, 0, 0, 0, // function length
0x42, // DW_CFA_advance_loc 2
0x0e, 0x10, // DW_CFA_def_cfa_offset 16
0x86, 0x02, // DW_CFA_offset r6, -8 * 2
0x43, // DW_CFA_advance_loc 3
0x0d, 0x06, // DW_CFA_def_cfa_register
0x47, // DW_CFA_advance_loc 10
0x0a, // DW_CFA_preserve_state
0x0c, 0x07, 0x08, // DW_CFA_def_cfa r7, 8
0x41, // DW_CFA_advance_loc 1
0x0b, // DW_CFA_restore_state
// NOTE: no additional CFA directives -- DW_CFA_restore_state
// is done before trap and it is last instruction in the function.
0, // padding
0, 0, 0, 0, // End of FDEs
]
);
assert_eq!(sink.1, 24);
assert_eq!(sink.2.len(), 1);
}
fn create_multi_return_function(call_conv: CallConv) -> Function {
let mut sig = Signature::new(call_conv);
sig.params.push(AbiParam::new(types::I32));
let mut func = Function::with_name_signature(ExternalName::user(0, 0), sig);
let block0 = func.dfg.make_block();
let v0 = func.dfg.append_block_param(block0, types::I32);
let block1 = func.dfg.make_block();
let block2 = func.dfg.make_block();
let mut pos = FuncCursor::new(&mut func);
pos.insert_block(block0);
pos.ins().brnz(v0, block2, &[]);
pos.ins().jump(block1, &[]);
pos.insert_block(block1);
pos.ins().return_(&[]);
pos.insert_block(block2);
pos.ins().trap(TrapCode::User(0));
func
}
}