machinst x64: New backend unwind (#2266)

Addresses unwind for experimental x64 backend. The preliminary code enables backtrace on SystemV call convension.
This commit is contained in:
Yury Delendik
2020-10-23 15:19:41 -05:00
committed by GitHub
parent 2702942050
commit de4af90af6
25 changed files with 554 additions and 29 deletions

View File

@@ -481,6 +481,7 @@ impl MachInstEmitInfo for EmitInfo {
impl MachInstEmit for Inst {
type State = EmitState;
type Info = EmitInfo;
type UnwindInfo = super::unwind::AArch64UnwindInfo;
fn emit(&self, sink: &mut MachBuffer<Inst>, emit_info: &Self::Info, state: &mut EmitState) {
// N.B.: we *must* not exceed the "worst-case size" used to compute

View File

@@ -29,6 +29,7 @@ pub mod args;
pub use self::args::*;
pub mod emit;
pub use self::emit::*;
pub mod unwind;
#[cfg(test)]
mod emit_tests;

View File

@@ -0,0 +1,15 @@
use super::*;
use crate::isa::unwind::UnwindInfo;
use crate::result::CodegenResult;
pub struct AArch64UnwindInfo;
impl UnwindInfoGenerator<Inst> for AArch64UnwindInfo {
fn create_unwind_info(
_context: UnwindInfoContext<Inst>,
_kind: UnwindInfoKind,
) -> CodegenResult<Option<UnwindInfo>> {
// TODO
Ok(None)
}
}

View File

@@ -77,6 +77,7 @@ impl MachBackend for AArch64Backend {
buffer,
frame_size,
disasm,
unwind_info: None,
})
}

View File

@@ -274,6 +274,7 @@ impl MachInstEmitInfo for EmitInfo {
impl MachInstEmit for Inst {
type Info = EmitInfo;
type State = EmitState;
type UnwindInfo = super::unwind::Arm32UnwindInfo;
fn emit(&self, sink: &mut MachBuffer<Inst>, emit_info: &Self::Info, state: &mut EmitState) {
let start_off = sink.cur_offset();

View File

@@ -22,6 +22,7 @@ mod emit;
pub use self::emit::*;
mod regs;
pub use self::regs::*;
pub mod unwind;
#[cfg(test)]
mod emit_tests;

View File

@@ -0,0 +1,15 @@
use super::*;
use crate::isa::unwind::UnwindInfo;
use crate::result::CodegenResult;
pub struct Arm32UnwindInfo;
impl UnwindInfoGenerator<Inst> for Arm32UnwindInfo {
fn create_unwind_info(
_context: UnwindInfoContext<Inst>,
_kind: UnwindInfoKind,
) -> CodegenResult<Option<UnwindInfo>> {
// TODO
Ok(None)
}
}

View File

@@ -73,6 +73,7 @@ impl MachBackend for Arm32Backend {
buffer,
frame_size,
disasm,
unwind_info: None,
})
}

View File

@@ -87,7 +87,6 @@ mod arm32;
#[cfg(feature = "arm64")]
pub(crate) mod aarch64;
#[cfg(feature = "unwind")]
pub mod unwind;
mod call_conv;

View File

@@ -2,16 +2,22 @@
#[cfg(feature = "enable-serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "unwind")]
pub mod systemv;
#[cfg(feature = "unwind")]
pub mod winx64;
/// Represents unwind information for a single function.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum UnwindInfo {
/// Windows x64 ABI unwind information.
#[cfg(feature = "unwind")]
WindowsX64(winx64::UnwindInfo),
/// System V ABI unwind information.
#[cfg(feature = "unwind")]
SystemV(systemv::UnwindInfo),
}

View File

@@ -1,6 +1,6 @@
//! System V ABI unwind information.
use crate::isa::{unwind::input, RegUnit};
use crate::isa::unwind::input;
use crate::result::{CodegenError, CodegenResult};
use alloc::vec::Vec;
use gimli::write::{Address, FrameDescriptionEntry};
@@ -95,9 +95,9 @@ impl Into<gimli::write::CallFrameInstruction> for CallFrameInstruction {
}
/// Maps UnwindInfo register to gimli's index space.
pub(crate) trait RegisterMapper {
/// Maps RegUnit.
fn map(&self, reg: RegUnit) -> Result<Register, RegisterMappingError>;
pub(crate) trait RegisterMapper<Reg> {
/// Maps Reg.
fn map(&self, reg: Reg) -> Result<Register, RegisterMappingError>;
/// Gets RSP in gimli's index space.
fn rsp(&self) -> Register;
}
@@ -113,9 +113,9 @@ pub struct UnwindInfo {
}
impl UnwindInfo {
pub(crate) fn build<'b>(
unwind: input::UnwindInfo<RegUnit>,
map_reg: &'b dyn RegisterMapper,
pub(crate) fn build<'b, Reg: PartialEq + Copy>(
unwind: input::UnwindInfo<Reg>,
map_reg: &'b dyn RegisterMapper<Reg>,
) -> CodegenResult<Self> {
use input::UnwindCode;
let mut builder = InstructionBuilder::new(unwind.word_size, map_reg);
@@ -181,16 +181,16 @@ impl UnwindInfo {
}
}
struct InstructionBuilder<'a> {
struct InstructionBuilder<'a, Reg: PartialEq + Copy> {
sp_offset: i32,
frame_register: Option<RegUnit>,
saved_state: Option<(i32, Option<RegUnit>)>,
map_reg: &'a dyn RegisterMapper,
frame_register: Option<Reg>,
saved_state: Option<(i32, Option<Reg>)>,
map_reg: &'a dyn RegisterMapper<Reg>,
instructions: Vec<(u32, CallFrameInstruction)>,
}
impl<'a> InstructionBuilder<'a> {
fn new(word_size: u8, map_reg: &'a (dyn RegisterMapper + 'a)) -> Self {
impl<'a, Reg: PartialEq + Copy> InstructionBuilder<'a, Reg> {
fn new(word_size: u8, map_reg: &'a (dyn RegisterMapper<Reg> + 'a)) -> Self {
Self {
sp_offset: word_size as i32, // CFA offset starts at word size offset to account for the return address on stack
saved_state: None,
@@ -200,7 +200,7 @@ impl<'a> InstructionBuilder<'a> {
}
}
fn save_reg(&mut self, offset: u32, reg: RegUnit) -> Result<(), RegisterMappingError> {
fn save_reg(&mut self, offset: u32, reg: Reg) -> Result<(), RegisterMappingError> {
// Pushes in the prologue are register saves, so record an offset of the save
self.instructions.push((
offset,
@@ -261,7 +261,7 @@ impl<'a> InstructionBuilder<'a> {
}
}
fn set_cfa_reg(&mut self, offset: u32, reg: RegUnit) -> Result<(), RegisterMappingError> {
fn set_cfa_reg(&mut self, offset: u32, reg: Reg) -> Result<(), RegisterMappingError> {
self.instructions.push((
offset,
CallFrameInstruction::CfaRegister(self.map_reg.map(reg)?),
@@ -270,7 +270,7 @@ impl<'a> InstructionBuilder<'a> {
Ok(())
}
fn restore_reg(&mut self, offset: u32, reg: RegUnit) -> Result<(), RegisterMappingError> {
fn restore_reg(&mut self, offset: u32, reg: Reg) -> Result<(), RegisterMappingError> {
// Update the CFA if this is the restore of the frame pointer register.
if Some(reg) == self.frame_register {
self.frame_register = None;

View File

@@ -20,6 +20,7 @@ mod emit;
#[cfg(test)]
mod emit_tests;
pub mod regs;
pub mod unwind;
use args::*;
use regs::{create_reg_universe_systemv, show_ireg_sized};
@@ -2706,6 +2707,7 @@ impl MachInstEmitInfo for EmitInfo {
impl MachInstEmit for Inst {
type State = EmitState;
type Info = EmitInfo;
type UnwindInfo = unwind::X64UnwindInfo;
fn emit(&self, sink: &mut MachBuffer<Inst>, info: &Self::Info, state: &mut Self::State) {
emit::emit(self, sink, info, state);

View File

@@ -0,0 +1,36 @@
use super::Inst;
use crate::isa::unwind::UnwindInfo;
use crate::machinst::{UnwindInfoContext, UnwindInfoGenerator, UnwindInfoKind};
use crate::result::CodegenResult;
#[cfg(feature = "unwind")]
pub use self::systemv::create_cie;
#[cfg(feature = "unwind")]
mod systemv;
pub struct X64UnwindInfo;
impl UnwindInfoGenerator<Inst> for X64UnwindInfo {
#[allow(unused_variables)]
fn create_unwind_info(
context: UnwindInfoContext<Inst>,
kind: UnwindInfoKind,
) -> CodegenResult<Option<UnwindInfo>> {
// Assumption: RBP is being used as the frame pointer for both calling conventions
// In the future, we should be omitting frame pointer as an optimization, so this will change
Ok(match kind {
#[cfg(feature = "unwind")]
UnwindInfoKind::SystemV => {
const WORD_SIZE: u8 = 8;
systemv::create_unwind_info(context, WORD_SIZE)?.map(UnwindInfo::SystemV)
}
#[cfg(feature = "unwind")]
UnwindInfoKind::Windows => {
//TODO winx64::create_unwind_info(context)?.map(|u| UnwindInfo::WindowsX64(u))
panic!();
}
UnwindInfoKind::None => None,
})
}
}

View File

@@ -0,0 +1,307 @@
//! Unwind information for System V ABI (x86-64).
use crate::isa::unwind::systemv::{RegisterMappingError, UnwindInfo};
use crate::isa::x64::inst::{
args::{AluRmiROpcode, Amode, RegMemImm, SyntheticAmode},
regs, Inst,
};
use crate::machinst::UnwindInfoContext;
use crate::result::CodegenResult;
use alloc::vec::Vec;
use gimli::{write::CommonInformationEntry, Encoding, Format, Register, X86_64};
use regalloc::{Reg, RegClass};
/// Creates a new x86-64 common information entry (CIE).
pub fn create_cie() -> CommonInformationEntry {
use gimli::write::CallFrameInstruction;
let mut entry = CommonInformationEntry::new(
Encoding {
address_size: 8,
format: Format::Dwarf32,
version: 1,
},
1, // Code alignment factor
-8, // Data alignment factor
X86_64::RA,
);
// Every frame will start with the call frame address (CFA) at RSP+8
// It is +8 to account for the push of the return address by the call instruction
entry.add_instruction(CallFrameInstruction::Cfa(X86_64::RSP, 8));
// Every frame will start with the return address at RSP (CFA-8 = RSP+8-8 = RSP)
entry.add_instruction(CallFrameInstruction::Offset(X86_64::RA, -8));
entry
}
/// Map Cranelift registers to their corresponding Gimli registers.
pub fn map_reg(reg: Reg) -> Result<Register, RegisterMappingError> {
// 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,
];
match reg.get_class() {
RegClass::I64 => {
// x86 GP registers have a weird mapping to DWARF registers, so we use a
// lookup table.
Ok(X86_GP_REG_MAP[reg.get_hw_encoding() as usize])
}
RegClass::V128 => Ok(X86_XMM_REG_MAP[reg.get_hw_encoding() as usize]),
_ => Err(RegisterMappingError::UnsupportedRegisterBank("class?")),
}
}
pub(crate) fn create_unwind_info(
context: UnwindInfoContext<Inst>,
word_size: u8,
) -> CodegenResult<Option<UnwindInfo>> {
use crate::isa::unwind::input::{self, UnwindCode};
let mut codes = Vec::new();
for i in context.prologue.clone() {
let i = i as usize;
let inst = &context.insts[i];
let offset = context.insts_layout[i];
match inst {
Inst::Push64 {
src: RegMemImm::Reg { reg },
} => {
codes.push(UnwindCode::StackAlloc {
offset,
size: word_size.into(),
});
codes.push(UnwindCode::SaveRegister {
offset,
reg: *reg,
stack_offset: 0,
});
}
Inst::MovRR { src, dst, .. } => {
if *src == regs::rsp() {
codes.push(UnwindCode::SetFramePointer {
offset,
reg: dst.to_reg(),
});
}
}
Inst::AluRmiR {
is_64: true,
op: AluRmiROpcode::Sub,
src: RegMemImm::Imm { simm32 },
dst,
..
} if dst.to_reg() == regs::rsp() => {
let imm = *simm32;
codes.push(UnwindCode::StackAlloc { offset, size: imm });
}
Inst::MovRM {
src,
dst: SyntheticAmode::Real(Amode::ImmReg { simm32, base }),
..
} if *base == regs::rsp() => {
// `mov reg, imm(rsp)`
let imm = *simm32;
codes.push(UnwindCode::SaveRegister {
offset,
reg: *src,
stack_offset: imm,
});
}
Inst::AluRmiR {
is_64: true,
op: AluRmiROpcode::Add,
src: RegMemImm::Imm { simm32 },
dst,
..
} if dst.to_reg() == regs::rsp() => {
let imm = *simm32;
codes.push(UnwindCode::StackDealloc { offset, size: imm });
}
_ => {}
}
}
let last_epilogue_end = context.len;
let epilogues_unwind_codes = context
.epilogues
.iter()
.map(|epilogue| {
let end = epilogue.end as usize - 1;
let end_offset = context.insts_layout[end];
if end_offset == last_epilogue_end {
// Do not remember/restore for very last epilogue.
return vec![];
}
let start = epilogue.start as usize;
let offset = context.insts_layout[start];
vec![
UnwindCode::RememberState { offset },
UnwindCode::RestoreState { offset: end_offset },
]
})
.collect();
let prologue_size = context.insts_layout[context.prologue.end as usize];
let unwind = input::UnwindInfo {
prologue_size,
prologue_unwind_codes: codes,
epilogues_unwind_codes,
function_size: context.len,
word_size,
};
struct RegisterMapper;
impl crate::isa::unwind::systemv::RegisterMapper<Reg> for RegisterMapper {
fn map(&self, reg: Reg) -> Result<u16, RegisterMappingError> {
Ok(map_reg(reg)?.0)
}
fn rsp(&self) -> u16 {
map_reg(regs::rsp()).unwrap().0
}
}
let map = RegisterMapper;
Ok(Some(UnwindInfo::build(unwind, &map)?))
}
#[cfg(test)]
mod tests {
use crate::cursor::{Cursor, FuncCursor};
use crate::ir::{
types, AbiParam, ExternalName, Function, InstBuilder, Signature, StackSlotData,
StackSlotKind,
};
use crate::isa::{lookup, CallConv};
use crate::settings::{builder, Flags};
use crate::Context;
use gimli::write::Address;
use std::str::FromStr;
use target_lexicon::triple;
#[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.compile(&*isa).expect("expected compilation");
let fde = match context
.create_unwind_info(isa.as_ref())
.expect("can create unwind info")
{
Some(crate::isa::unwind::UnwindInfo::SystemV(info)) => {
info.to_fde(Address::Constant(1234))
}
_ => panic!("expected unwind information"),
};
assert_eq!(format!("{:?}", fde), "FrameDescriptionEntry { address: Constant(1234), length: 13, lsda: None, instructions: [(1, CfaOffset(16)), (1, Offset(Register(6), -16)), (4, CfaRegister(Register(6)))] }");
}
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.compile(&*isa).expect("expected compilation");
let fde = match context
.create_unwind_info(isa.as_ref())
.expect("can create unwind info")
{
Some(crate::isa::unwind::UnwindInfo::SystemV(info)) => {
info.to_fde(Address::Constant(4321))
}
_ => panic!("expected unwind information"),
};
assert_eq!(format!("{:?}", fde), "FrameDescriptionEntry { address: Constant(4321), length: 23, lsda: None, instructions: [(1, CfaOffset(16)), (1, Offset(Register(6), -16)), (4, CfaRegister(Register(6))), (16, RememberState), (18, RestoreState)] }");
}
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().return_(&[]);
func
}
}

View File

@@ -59,6 +59,7 @@ impl MachBackend for X64Backend {
let buffer = vcode.emit();
let buffer = buffer.finish();
let frame_size = vcode.frame_size();
let unwind_info = vcode.unwind_info()?;
let disasm = if want_disasm {
Some(vcode.show_rru(Some(&create_reg_universe_systemv(flags))))
@@ -70,6 +71,7 @@ impl MachBackend for X64Backend {
buffer,
frame_size,
disasm,
unwind_info,
})
}
@@ -100,6 +102,12 @@ impl MachBackend for X64Backend {
// underflow of a subtract (carry is borrow for subtract).
IntCC::UnsignedGreaterThanOrEqual
}
#[cfg(feature = "unwind")]
fn create_systemv_cie(&self) -> Option<gimli::write::CommonInformationEntry> {
// By default, an ISA cannot create a System V CIE
Some(inst::unwind::create_cie())
}
}
/// Create a new `isa::Builder`.

View File

@@ -114,7 +114,7 @@ pub(crate) fn create_unwind_info(
};
struct RegisterMapper<'a, 'b>(&'a (dyn TargetIsa + 'b));
impl<'a, 'b> crate::isa::unwind::systemv::RegisterMapper for RegisterMapper<'a, 'b> {
impl<'a, 'b> crate::isa::unwind::systemv::RegisterMapper<RegUnit> for RegisterMapper<'a, 'b> {
fn map(&self, reg: RegUnit) -> Result<u16, RegisterMappingError> {
Ok(map_reg(self.0, reg)?.0)
}