use super::registers::RU;
use crate::ir::{Function, InstructionData, Opcode};
use crate::isa::{CallConv, RegUnit, TargetIsa};
use alloc::vec::Vec;
use byteorder::{ByteOrder, LittleEndian};
const SMALL_ALLOC_MAX_SIZE: u32 = 128;
const LARGE_ALLOC_16BIT_MAX_SIZE: u32 = 524280;
fn write_u16<T: ByteOrder>(mem: &mut Vec<u8>, v: u16) {
let mut buf = [0; 2];
T::write_u16(&mut buf, v);
mem.extend(buf.iter());
}
fn write_u32<T: ByteOrder>(mem: &mut Vec<u8>, v: u32) {
let mut buf = [0; 4];
T::write_u32(&mut buf, v);
mem.extend(buf.iter());
}
#[derive(Debug, PartialEq, Eq)]
enum UnwindCode {
PushRegister { offset: u8, reg: RegUnit },
StackAlloc { offset: u8, size: u32 },
SetFramePointer { offset: u8, sp_offset: u8 },
}
impl UnwindCode {
fn emit(&self, mem: &mut Vec<u8>) {
enum UnwindOperation {
PushNonvolatileRegister,
LargeStackAlloc,
SmallStackAlloc,
SetFramePointer,
}
match self {
Self::PushRegister { offset, reg } => {
mem.push(*offset);
mem.push(((*reg as u8) << 4) | (UnwindOperation::PushNonvolatileRegister as u8));
}
Self::StackAlloc { offset, size } => {
assert!(*size >= 8);
assert!((*size % 8) == 0);
mem.push(*offset);
if *size <= SMALL_ALLOC_MAX_SIZE {
mem.push(
((((*size - 8) / 8) as u8) << 4) | UnwindOperation::SmallStackAlloc as u8,
);
} else if *size <= LARGE_ALLOC_16BIT_MAX_SIZE {
mem.push(UnwindOperation::LargeStackAlloc as u8);
write_u16::<LittleEndian>(mem, (*size / 8) as u16);
} else {
mem.push((1 << 4) | (UnwindOperation::LargeStackAlloc as u8));
write_u32::<LittleEndian>(mem, *size);
}
}
Self::SetFramePointer { offset, sp_offset } => {
mem.push(*offset);
mem.push((*sp_offset << 4) | (UnwindOperation::SetFramePointer as u8));
}
};
}
fn node_count(&self) -> usize {
match self {
Self::StackAlloc { size, .. } => {
if *size <= SMALL_ALLOC_MAX_SIZE {
1
} else if *size <= LARGE_ALLOC_16BIT_MAX_SIZE {
2
} else {
3
}
}
_ => 1,
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct UnwindInfo {
flags: u8,
prologue_size: u8,
frame_register: Option<RegUnit>,
frame_register_offset: u8,
unwind_codes: Vec<UnwindCode>,
}
impl UnwindInfo {
pub fn try_from_func(
func: &Function,
isa: &dyn TargetIsa,
frame_register: Option<RegUnit>,
) -> Option<Self> {
if func.signature.call_conv != CallConv::WindowsFastcall || func.prologue_end.is_none() {
return None;
}
let prologue_end = func.prologue_end.unwrap();
let entry_block = func.layout.ebbs().nth(0).expect("missing entry block");
let mut stack_size = None;
let mut prologue_size = 0;
let mut unwind_codes = Vec::new();
let mut found_end = false;
for (offset, inst, size) in func.inst_offsets(entry_block, &isa.encoding_info()) {
if (offset + size) > 255 {
panic!("function prologues cannot exceed 255 bytes in size for Windows x64");
}
prologue_size += size;
let unwind_offset = (offset + size) as u8;
match func.dfg[inst] {
InstructionData::Unary { opcode, arg } => {
match opcode {
Opcode::X86Push => {
unwind_codes.push(UnwindCode::PushRegister {
offset: unwind_offset,
reg: func.locations[arg].unwrap_reg(),
});
}
Opcode::AdjustSpDown => {
unwind_codes.push(UnwindCode::StackAlloc {
offset: unwind_offset,
size: stack_size
.expect("expected a previous stack size instruction"),
});
}
_ => {}
}
}
InstructionData::CopySpecial { src, dst, .. } => {
if let Some(frame_register) = frame_register {
if src == (RU::rsp as RegUnit) && dst == frame_register {
unwind_codes.push(UnwindCode::SetFramePointer {
offset: unwind_offset,
sp_offset: 0,
});
}
}
}
InstructionData::UnaryImm { opcode, imm } => {
match opcode {
Opcode::Iconst => {
let imm: i64 = imm.into();
assert!(imm <= core::u32::MAX as i64);
assert!(stack_size.is_none());
stack_size = Some(imm as u32);
}
Opcode::AdjustSpDownImm => {
let imm: i64 = imm.into();
assert!(imm <= core::u32::MAX as i64);
unwind_codes.push(UnwindCode::StackAlloc {
offset: unwind_offset,
size: imm as u32,
});
}
_ => {}
}
}
_ => {}
};
if inst == prologue_end {
found_end = true;
break;
}
}
if !found_end {
return None;
}
Some(Self {
flags: 0,
prologue_size: prologue_size as u8,
frame_register,
frame_register_offset: 0,
unwind_codes,
})
}
pub fn size(&self) -> usize {
let node_count = self.node_count();
assert!(self.flags == 0);
4 + (node_count * 2) + if (node_count & 1) == 1 { 2 } else { 0 }
}
pub fn node_count(&self) -> usize {
self.unwind_codes
.iter()
.fold(0, |nodes, c| nodes + c.node_count())
}
pub fn emit(&self, mem: &mut Vec<u8>) {
const UNWIND_INFO_VERSION: u8 = 1;
let size = self.size();
let offset = mem.len();
assert_eq!(offset % 4, 0);
mem.reserve(offset + size);
let node_count = self.node_count();
assert!(node_count <= 256);
mem.push((self.flags << 3) | UNWIND_INFO_VERSION);
mem.push(self.prologue_size);
mem.push(node_count as u8);
if let Some(reg) = self.frame_register {
mem.push((self.frame_register_offset << 4) | reg as u8);
} else {
mem.push(0);
}
for code in self.unwind_codes.iter().rev() {
code.emit(mem);
}
if (node_count & 1) == 1 {
write_u16::<LittleEndian>(mem, 0);
}
assert_eq!(mem.len() - offset, size);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cursor::{Cursor, FuncCursor};
use crate::ir::{ExternalName, InstBuilder, Signature, StackSlotData, StackSlotKind};
use crate::isa::{lookup, CallConv};
use crate::settings::{builder, Flags};
use crate::Context;
use std::str::FromStr;
use target_lexicon::triple;
#[test]
fn test_wrong_calling_convention() {
let isa = lookup(triple!("x86_64"))
.expect("expect x86 ISA")
.finish(Flags::new(builder()));
let mut context = Context::for_function(create_function(CallConv::SystemV, None));
context.compile(&*isa).expect("expected compilation");
assert_eq!(UnwindInfo::try_from_func(&context.func, &*isa, None), None);
}
#[test]
fn test_small_alloc() {
let isa = lookup(triple!("x86_64"))
.expect("expect x86 ISA")
.finish(Flags::new(builder()));
let mut context = Context::for_function(create_function(
CallConv::WindowsFastcall,
Some(StackSlotData::new(StackSlotKind::ExplicitSlot, 64)),
));
context.compile(&*isa).expect("expected compilation");
let unwind = UnwindInfo::try_from_func(&context.func, &*isa, Some(RU::rbp.into()))
.expect("expected unwind info");
assert_eq!(
unwind,
UnwindInfo {
flags: 0,
prologue_size: 9,
frame_register: Some(RU::rbp.into()),
frame_register_offset: 0,
unwind_codes: vec![
UnwindCode::PushRegister {
offset: 2,
reg: RU::rbp.into()
},
UnwindCode::SetFramePointer {
offset: 5,
sp_offset: 0
},
UnwindCode::StackAlloc {
offset: 9,
size: 64 + 32
}
]
}
);
assert_eq!(unwind.size(), 12);
let mut mem = Vec::new();
unwind.emit(&mut mem);
assert_eq!(
mem,
[
0x01,
0x09,
0x03,
0x05,
0x09,
0xB2,
0x05,
0x03,
0x02,
0x50,
0x00,
0x00,
]
);
}
#[test]
fn test_medium_alloc() {
let isa = lookup(triple!("x86_64"))
.expect("expect x86 ISA")
.finish(Flags::new(builder()));
let mut context = Context::for_function(create_function(
CallConv::WindowsFastcall,
Some(StackSlotData::new(StackSlotKind::ExplicitSlot, 10000)),
));
context.compile(&*isa).expect("expected compilation");
let unwind = UnwindInfo::try_from_func(&context.func, &*isa, Some(RU::rbp.into()))
.expect("expected unwind info");
assert_eq!(
unwind,
UnwindInfo {
flags: 0,
prologue_size: 27,
frame_register: Some(RU::rbp.into()),
frame_register_offset: 0,
unwind_codes: vec![
UnwindCode::PushRegister {
offset: 2,
reg: RU::rbp.into()
},
UnwindCode::SetFramePointer {
offset: 5,
sp_offset: 0
},
UnwindCode::StackAlloc {
offset: 27,
size: 10000 + 32
}
]
}
);
assert_eq!(unwind.size(), 12);
let mut mem = Vec::new();
unwind.emit(&mut mem);
assert_eq!(
mem,
[
0x01,
0x1B,
0x04,
0x05,
0x1B,
0x01,
0xE6,
0x04,
0x05,
0x03,
0x02,
0x50,
]
);
}
#[test]
fn test_large_alloc() {
let isa = lookup(triple!("x86_64"))
.expect("expect x86 ISA")
.finish(Flags::new(builder()));
let mut context = Context::for_function(create_function(
CallConv::WindowsFastcall,
Some(StackSlotData::new(StackSlotKind::ExplicitSlot, 1000000)),
));
context.compile(&*isa).expect("expected compilation");
let unwind = UnwindInfo::try_from_func(&context.func, &*isa, Some(RU::rbp.into()))
.expect("expected unwind info");
assert_eq!(
unwind,
UnwindInfo {
flags: 0,
prologue_size: 27,
frame_register: Some(RU::rbp.into()),
frame_register_offset: 0,
unwind_codes: vec![
UnwindCode::PushRegister {
offset: 2,
reg: RU::rbp.into()
},
UnwindCode::SetFramePointer {
offset: 5,
sp_offset: 0
},
UnwindCode::StackAlloc {
offset: 27,
size: 1000000 + 32
}
]
}
);
assert_eq!(unwind.size(), 16);
let mut mem = Vec::new();
unwind.emit(&mut mem);
assert_eq!(
mem,
[
0x01,
0x1B,
0x05,
0x05,
0x1B,
0x11,
0x60,
0x42,
0x0F,
0x00,
0x05,
0x03,
0x02,
0x50,
0x00,
0x00,
]
);
}
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 ebb0 = func.dfg.make_ebb();
let mut pos = FuncCursor::new(&mut func);
pos.insert_ebb(ebb0);
pos.ins().return_(&[]);
if let Some(stack_slot) = stack_slot {
func.stack_slots.push(stack_slot);
}
func
}
}