use rucc_mir::CfiOp;
use rucc_object::{Extent, Reference, Reloc, Unwind};
use rucc_target::CallRegs;
const CODE_ALIGN: u64 = 1;
const PCREL_SDATA4: u8 = 0x1b;
const NOP: u8 = 0x00;
const ADVANCE_LOC1: u8 = 0x02;
const ADVANCE_LOC2: u8 = 0x03;
const ADVANCE_LOC4: u8 = 0x04;
const OFFSET_EXTENDED: u8 = 0x05;
const RESTORE_EXTENDED: u8 = 0x06;
const REMEMBER_STATE: u8 = 0x0a;
const RESTORE_STATE: u8 = 0x0b;
const DEF_CFA: u8 = 0x0c;
const DEF_CFA_REGISTER: u8 = 0x0d;
const DEF_CFA_OFFSET: u8 = 0x0e;
const ADVANCE_LOC: u8 = 0x40;
const OFFSET: u8 = 0x80;
const RESTORE: u8 = 0xc0;
const SHORT_REG: u16 = 63;
pub(crate) type Rows = Vec<(usize, CfiOp)>;
pub(crate) fn table(funcs: &[Extent], rows: &[Rows], conv: &CallRegs) -> Unwind {
debug_assert_eq!(funcs.len(), rows.len(), "a record per function");
if funcs.is_empty() {
return Unwind::default();
}
let align = usize::try_from(conv.word).expect("a pointer width").max(1);
let mut table = Table { out: Unwind::default(), cie: 0, slot: -i64::from(conv.word), align };
table.header(conv);
for (func, rows) in funcs.iter().zip(rows) {
table.record(func, rows);
}
table.out
}
struct Table {
out: Unwind,
cie: usize,
slot: i64,
align: usize,
}
impl Table {
fn header(&mut self, conv: &CallRegs) {
let start = self.out.bytes.len();
self.cie = start;
self.out.bytes.extend_from_slice(&0u32.to_le_bytes());
self.out.bytes.extend_from_slice(&0u32.to_le_bytes());
self.out.bytes.push(1);
self.out.bytes.extend_from_slice(b"zR\0");
uleb(&mut self.out.bytes, CODE_ALIGN);
sleb(&mut self.out.bytes, self.slot);
uleb(&mut self.out.bytes, u64::from(conv.dwarf_return_address));
uleb(&mut self.out.bytes, 1);
self.out.bytes.push(PCREL_SDATA4);
let sp = conv
.dwarf(conv.int_class, conv.stack_pointer)
.expect("the stack pointer has a number in the table beside the register file");
self.out.bytes.push(DEF_CFA);
uleb(&mut self.out.bytes, u64::from(sp));
uleb(&mut self.out.bytes, u64::from(conv.return_address));
let below = -i32::try_from(conv.return_address).expect("a word");
self.saved(conv.dwarf_return_address, below);
self.pad(start);
}
fn record(&mut self, func: &Extent, rows: &Rows) {
let start = self.out.bytes.len();
self.out.bytes.extend_from_slice(&0u32.to_le_bytes());
let back = u32::try_from(self.out.bytes.len() - self.cie).expect("an object this size");
self.out.bytes.extend_from_slice(&back.to_le_bytes());
self.out.relocs.push(Reloc {
at: self.out.bytes.len(),
symbol: func.name.clone(),
kind: Reference::Data,
addend: 0,
});
self.out.bytes.extend_from_slice(&0u32.to_le_bytes());
let len = u32::try_from(func.len).expect("a function this size");
self.out.bytes.extend_from_slice(&len.to_le_bytes());
uleb(&mut self.out.bytes, 0);
let mut at = 0;
for &(offset, op) in rows {
self.advance(offset - at);
at = offset;
self.row(op);
}
self.pad(start);
}
fn row(&mut self, op: CfiOp) {
match op {
CfiOp::DefCfa { reg, offset } => {
self.out.bytes.push(DEF_CFA);
uleb(&mut self.out.bytes, u64::from(reg));
uleb(&mut self.out.bytes, above(offset));
}
CfiOp::DefCfaOffset(offset) => {
self.out.bytes.push(DEF_CFA_OFFSET);
uleb(&mut self.out.bytes, above(offset));
}
CfiOp::DefCfaRegister(reg) => {
self.out.bytes.push(DEF_CFA_REGISTER);
uleb(&mut self.out.bytes, u64::from(reg));
}
CfiOp::Offset { reg, offset } => self.saved(reg, offset),
CfiOp::Restore(reg) if reg <= SHORT_REG => {
self.out.bytes.push(RESTORE | small(reg));
}
CfiOp::Restore(reg) => {
self.out.bytes.push(RESTORE_EXTENDED);
uleb(&mut self.out.bytes, u64::from(reg));
}
CfiOp::RememberState => self.out.bytes.push(REMEMBER_STATE),
CfiOp::RestoreState => self.out.bytes.push(RESTORE_STATE),
}
}
fn saved(&mut self, reg: u16, offset: i32) {
let factored = i64::from(offset) / self.slot;
debug_assert_eq!(
factored * self.slot,
i64::from(offset),
"a slot is a whole number of slots below the end of the frame"
);
let factored = u64::try_from(factored).expect("a slot below the end of the frame");
if reg <= SHORT_REG {
self.out.bytes.push(OFFSET | small(reg));
} else {
self.out.bytes.push(OFFSET_EXTENDED);
uleb(&mut self.out.bytes, u64::from(reg));
}
uleb(&mut self.out.bytes, factored);
}
fn advance(&mut self, by: usize) {
let by = u64::try_from(by).expect("a function this size") / CODE_ALIGN;
match by {
0 => {}
1..=0x3f => {
let by = u8::try_from(by).expect("checked just above");
self.out.bytes.push(ADVANCE_LOC | by);
}
0x40..=0xff => {
self.out.bytes.push(ADVANCE_LOC1);
self.out.bytes.push(u8::try_from(by).expect("checked just above"));
}
0x100..=0xffff => {
self.out.bytes.push(ADVANCE_LOC2);
let by = u16::try_from(by).expect("checked just above");
self.out.bytes.extend_from_slice(&by.to_le_bytes());
}
_ => {
self.out.bytes.push(ADVANCE_LOC4);
let by = u32::try_from(by).expect("a function this size");
self.out.bytes.extend_from_slice(&by.to_le_bytes());
}
}
}
fn pad(&mut self, start: usize) {
while (self.out.bytes.len() - start) % self.align != 0 {
self.out.bytes.push(NOP);
}
let len = u32::try_from(self.out.bytes.len() - start - 4).expect("a record this size");
self.out.bytes[start..start + 4].copy_from_slice(&len.to_le_bytes());
}
}
fn small(reg: u16) -> u8 {
u8::try_from(reg).expect("a register number the caller checked")
}
fn above(offset: i32) -> u64 {
u64::try_from(offset).expect("a frame that ends above the stack pointer")
}
fn uleb(bytes: &mut Vec<u8>, mut value: u64) {
loop {
let byte = u8::try_from(value & 0x7f).expect("seven bits");
value >>= 7;
if value == 0 {
bytes.push(byte);
return;
}
bytes.push(byte | 0x80);
}
}
fn sleb(bytes: &mut Vec<u8>, mut value: i64) {
loop {
let byte = u8::try_from(value & 0x7f).expect("seven bits");
value >>= 7;
let done = (value == 0 && byte & 0x40 == 0) || (value == -1 && byte & 0x40 != 0);
if done {
bytes.push(byte);
return;
}
bytes.push(byte | 0x80);
}
}