use rucc_mir::CfiOp;
use rucc_object::{Extent, Marker, Reference, Reloc, Unwind};
use rucc_target::{CallRegs, ObjectFormat};
use crate::Error;
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,
format: ObjectFormat,
) -> Result<Unwind, Error> {
debug_assert_eq!(funcs.len(), rows.len(), "a record per function");
if funcs.is_empty() {
return Ok(Unwind::default());
}
match format {
ObjectFormat::Elf => Ok(dwarf(funcs, rows, conv)),
ObjectFormat::Coff => windows(funcs, rows, conv),
ObjectFormat::MachO | ObjectFormat::Wasm => Ok(Unwind::default()),
}
}
fn dwarf(funcs: &[Extent], rows: &[Rows], conv: &CallRegs) -> Unwind {
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,
after: 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());
}
}
const PUSH_NONVOL: u8 = 0;
const ALLOC_LARGE: u8 = 1;
const ALLOC_SMALL: u8 = 2;
const SAVE_NONVOL: u8 = 4;
const SAVE_NONVOL_FAR: u8 = 5;
const SAVE_XMM128: u8 = 8;
const SAVE_XMM128_FAR: u8 = 9;
const SMALL_FRAME: i64 = 128;
enum Step {
Push(u16),
Alloc(i64),
Save { reg: u16, from: i64 },
}
fn windows(funcs: &[Extent], rows: &[Rows], conv: &CallRegs) -> Result<Unwind, Error> {
let mut out = Unwind::default();
for (func, rows) in funcs.iter().zip(rows) {
let name = format!("$unwind${}", func.name);
let at = out.info.len();
describe(&mut out.info, func, rows, conv)?;
out.labels.push(Marker { name: name.clone(), at });
let len = i64::try_from(func.len).expect("a function this size");
for (symbol, addend) in [(func.name.clone(), 0), (func.name.clone(), len), (name, 0)] {
out.relocs.push(Reloc {
at: out.bytes.len(),
symbol,
kind: Reference::Image,
addend,
after: 0,
});
out.bytes.extend_from_slice(&0u32.to_le_bytes());
}
}
Ok(out)
}
fn describe(info: &mut Vec<u8>, func: &Extent, rows: &Rows, conv: &CallRegs) -> Result<(), Error> {
let codes = codes(func, rows, conv)?;
let prologue = codes.last().map_or(0, |code| code[0]);
let nodes = codes.iter().map(Vec::len).sum::<usize>() / 2;
let count = u8::try_from(nodes).map_err(|_| {
let why = format!("a prologue of {nodes} unwind slots, more than a record holds");
frame(func, why)
})?;
info.extend_from_slice(&[1, prologue, count, 0]);
for code in codes.iter().rev() {
info.extend_from_slice(code);
}
if nodes % 2 != 0 {
info.extend_from_slice(&[0, 0]);
}
Ok(())
}
fn codes(func: &Extent, rows: &Rows, conv: &CallRegs) -> Result<Vec<Vec<u8>>, Error> {
let end = rows.iter().position(|(_, op)| *op == CfiOp::RememberState).unwrap_or(rows.len());
let rows = &rows[..end];
let word = i64::from(conv.word);
let mut below = i64::from(conv.return_address);
let mut steps = Vec::new();
let mut rest = rows;
while let Some(&(at, _)) = rest.first() {
let len = rest.iter().take_while(|(offset, _)| *offset == at).count();
let (group, next) = rest.split_at(len);
rest = next;
let at = u8::try_from(at).map_err(|_| {
frame(func, "a prologue longer than a record can count in a byte".to_owned())
})?;
match group {
[(_, CfiOp::DefCfaOffset(moved)), (_, CfiOp::Offset { reg, offset })]
if i64::from(*moved) - below == word
&& i64::from(*offset) == -i64::from(*moved) =>
{
below += word;
steps.push((at, Step::Push(*reg)));
}
[(_, CfiOp::DefCfaOffset(moved))] => {
steps.push((at, Step::Alloc(i64::from(*moved) - below)));
below = i64::from(*moved);
}
[(_, CfiOp::Offset { reg, offset })] => {
steps.push((at, Step::Save { reg: *reg, from: i64::from(*offset) }));
}
[(_, CfiOp::DefCfaRegister(_))] => {
let why = "a frame pointer established before the frame is taken";
return Err(frame(func, why.to_owned()));
}
[(_, CfiOp::DefCfa { .. })] => {
let why = "a stack walked a page at a time, whose frame is counted from a scratch \
register";
return Err(frame(func, why.to_owned()));
}
_ => return Err(frame(func, "a prologue row this cannot read".to_owned())),
}
}
steps.into_iter().map(|(at, step)| code(func, conv, at, step, below)).collect()
}
fn code(func: &Extent, conv: &CallRegs, at: u8, step: Step, below: i64) -> Result<Vec<u8>, Error> {
match step {
Step::Push(reg) => Ok(vec![at, PUSH_NONVOL | machine(func, conv, reg)? << 4]),
Step::Alloc(size)
if (word_size(conv)..=SMALL_FRAME).contains(&size) && size % word_size(conv) == 0 =>
{
let steps = u8::try_from(size / word_size(conv) - 1).expect("a frame this small");
Ok(vec![at, ALLOC_SMALL | steps << 4])
}
Step::Alloc(size) => large(func, at, size, word_size(conv)),
Step::Save { reg, from } => slot(func, conv, at, reg, below + from),
}
}
fn word_size(conv: &CallRegs) -> i64 {
i64::from(conv.word).max(1)
}
fn large(func: &Extent, at: u8, size: i64, word: i64) -> Result<Vec<u8>, Error> {
if size <= 0 || size % word != 0 {
let why = format!("a frame of {size} bytes, not a whole number of slots");
return Err(frame(func, why));
}
let mut out = vec![at, ALLOC_LARGE];
if let Ok(slots) = u16::try_from(size / word) {
out.extend_from_slice(&slots.to_le_bytes());
return Ok(out);
}
let bytes = u32::try_from(size).map_err(|_| {
frame(func, format!("a frame of {size} bytes, larger than a record can say"))
})?;
out[1] |= 1 << 4;
out.extend_from_slice(&bytes.to_le_bytes());
Ok(out)
}
fn slot(func: &Extent, conv: &CallRegs, at: u8, reg: u16, above: i64) -> Result<Vec<u8>, Error> {
if above < 0 {
let why = format!("a register saved {} bytes below its own frame", -above);
return Err(frame(func, why));
}
let bytes = u32::try_from(above).map_err(|_| {
frame(func, format!("a register saved {above} bytes up, further than a record reaches"))
})?;
let vector = conv.machine(conv.int_class, reg).is_none();
let (near, far, step) = if vector {
(SAVE_XMM128, SAVE_XMM128_FAR, 16)
} else {
(SAVE_NONVOL, SAVE_NONVOL_FAR, u32::try_from(word_size(conv)).expect("a pointer width"))
};
let number = machine(func, conv, reg)?;
let scaled = (above % i64::from(step) == 0).then(|| u16::try_from(bytes / step).ok()).flatten();
let mut out = vec![at, if scaled.is_some() { near } else { far } | number << 4];
match scaled {
Some(scaled) => out.extend_from_slice(&scaled.to_le_bytes()),
None => out.extend_from_slice(&bytes.to_le_bytes()),
}
Ok(out)
}
fn machine(func: &Extent, conv: &CallRegs, reg: u16) -> Result<u8, Error> {
let found = conv
.machine(conv.int_class, reg)
.or_else(|| conv.machine(conv.sse_class, reg))
.map(|reg| reg.number())
.filter(|number| *number < 16);
found.ok_or_else(|| {
frame(
func,
format!("a register saved under DWARF number {reg}, which this machine has none of"),
)
})
}
fn frame(func: &Extent, why: String) -> Error {
Error::Frame { func: func.name.clone(), why }
}
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);
}
}
#[cfg(test)]
mod tests {
use rucc_object::{Binding, Visibility};
use rucc_target::x86_64::WIN64;
use super::*;
const RBP: u16 = 6;
const RBX: u16 = 3;
const R12: u16 = 12;
const XMM6: u16 = 23;
fn func(name: &str, len: usize) -> Extent {
Extent {
name: name.to_owned(),
start: 0,
len,
align: 16,
binding: Binding::Global,
visibility: Visibility::Default,
patch: None,
}
}
fn info(rows: Rows) -> Vec<u8> {
let out = table(&[func("f", 64)], &[rows], &WIN64, ObjectFormat::Coff)
.expect("a prologue this can describe");
out.info
}
fn refused(rows: Rows) -> String {
let out = table(&[func("f", 64)], &[rows], &WIN64, ObjectFormat::Coff)
.expect_err("a prologue this cannot describe");
out.to_string()
}
#[test]
fn a_prologue_of_pushes_and_a_frame_is_the_codes_that_undo_it() {
let rows = vec![
(1, CfiOp::DefCfaOffset(16)),
(1, CfiOp::Offset { reg: RBP, offset: -16 }),
(2, CfiOp::DefCfaOffset(24)),
(2, CfiOp::Offset { reg: RBX, offset: -24 }),
(8, CfiOp::DefCfaOffset(56)),
(8, CfiOp::RememberState),
];
let want = [
vec![1, 8, 3, 0],
vec![8, ALLOC_SMALL | (3 << 4)],
vec![2, PUSH_NONVOL | (3 << 4)],
vec![1, PUSH_NONVOL | (5 << 4)],
vec![0, 0],
];
assert_eq!(info(rows), want.concat());
}
#[test]
fn a_frame_too_big_for_a_code_goes_in_the_nodes_behind_it() {
let one = vec![(4, CfiOp::DefCfaOffset(8 + 4096)), (4, CfiOp::RememberState)];
assert_eq!(info(one), vec![1, 4, 2, 0, 4, ALLOC_LARGE, 0x00, 0x02]);
let huge = vec![(7, CfiOp::DefCfaOffset(8 + 8 * 0x1_0000)), (7, CfiOp::RememberState)];
let want = vec![1, 7, 3, 0, 7, ALLOC_LARGE | (1 << 4), 0x00, 0x00, 0x08, 0x00, 0, 0];
assert_eq!(info(huge), want);
}
#[test]
fn a_register_saved_in_a_slot_is_measured_from_the_end_of_the_prologue() {
let rows = vec![
(1, CfiOp::DefCfaOffset(16)),
(1, CfiOp::Offset { reg: RBP, offset: -16 }),
(8, CfiOp::DefCfaOffset(56)),
(14, CfiOp::Offset { reg: XMM6, offset: -40 }),
(14, CfiOp::RememberState),
];
let want = [
vec![1, 14, 4, 0],
vec![14, SAVE_XMM128 | (6 << 4), 0x01, 0x00],
vec![8, ALLOC_SMALL | (4 << 4)],
vec![1, PUSH_NONVOL | (5 << 4)],
];
assert_eq!(info(rows), want.concat());
}
#[test]
fn a_general_purpose_register_in_a_slot_counts_in_words() {
let rows = vec![
(8, CfiOp::DefCfaOffset(72)),
(13, CfiOp::Offset { reg: R12, offset: -48 }),
(13, CfiOp::RememberState),
];
let want = [
vec![1, 13, 3, 0],
vec![13, SAVE_NONVOL | (12 << 4), 0x03, 0x00],
vec![8, ALLOC_SMALL | (7 << 4)],
vec![0, 0],
];
assert_eq!(info(rows), want.concat());
}
#[test]
fn a_leaf_gets_an_empty_description_rather_than_none() {
assert_eq!(info(Vec::new()), vec![1, 0, 0, 0]);
}
#[test]
fn every_function_gets_a_row_of_three_places_the_linker_fills_in() {
let funcs = [func("one", 32), func("two", 48)];
let out = table(&funcs, &[Vec::new(), Vec::new()], &WIN64, ObjectFormat::Coff)
.expect("two leaves");
assert_eq!(out.bytes, vec![0; 24], "three empty fields per function");
let places: Vec<_> =
out.relocs.iter().map(|reloc| (reloc.symbol.as_str(), reloc.addend)).collect();
assert_eq!(
places,
vec![
("one", 0),
("one", 32),
("$unwind$one", 0),
("two", 0),
("two", 48),
("$unwind$two", 0),
]
);
assert!(out.relocs.iter().all(|reloc| reloc.kind == Reference::Image));
let labels: Vec<_> =
out.labels.iter().map(|label| (label.name.as_str(), label.at)).collect();
assert_eq!(labels, vec![("$unwind$one", 0), ("$unwind$two", 4)]);
}
#[test]
fn a_prologue_this_cannot_describe_is_refused_by_name() {
let pointer = vec![
(1, CfiOp::DefCfaOffset(16)),
(1, CfiOp::Offset { reg: RBP, offset: -16 }),
(4, CfiOp::DefCfaRegister(RBP)),
(4, CfiOp::RememberState),
];
let why = refused(pointer);
assert!(why.contains("'f'"), "{why}");
assert!(why.contains("frame pointer"), "{why}");
let walked = vec![(9, CfiOp::DefCfa { reg: 0, offset: 65544 }), (9, CfiOp::RememberState)];
let why = refused(walked);
assert!(why.contains("a page at a time"), "{why}");
let long = vec![(300, CfiOp::DefCfaOffset(16)), (300, CfiOp::RememberState)];
assert!(refused(long).contains("longer than"), "a prologue no record can count in a byte");
}
#[test]
fn a_format_whose_table_is_not_written_yet_gets_no_section() {
let rows = vec![vec![(1, CfiOp::DefCfaOffset(16))]];
let mach = table(&[func("f", 8)], &rows, &WIN64, ObjectFormat::MachO).expect("nothing");
assert_eq!(mach, Unwind::default());
}
}