use std::fmt::Write as _;
use rucc_object::{Binding, Place};
use rucc_target::ObjectFormat;
use crate::data::Variable;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Directives {
Elf,
MachO,
Coff,
}
impl Directives {
#[must_use]
pub const fn of(format: ObjectFormat) -> Directives {
match format {
ObjectFormat::Elf => Directives::Elf,
ObjectFormat::MachO => Directives::MachO,
ObjectFormat::Coff => Directives::Coff,
}
}
#[must_use]
pub const fn symbol(self) -> &'static str {
match self {
Directives::Elf | Directives::Coff => "",
Directives::MachO => "_",
}
}
#[must_use]
pub const fn local(self) -> &'static str {
match self {
Directives::Elf | Directives::Coff => ".L",
Directives::MachO => "L",
}
}
#[must_use]
pub const fn text(self) -> &'static str {
match self {
Directives::Elf | Directives::Coff => "\t.text",
Directives::MachO => "\t.section\t__TEXT,__text,regular,pure_instructions",
}
}
pub fn open(self, out: &mut String, name: &str) {
let symbol = self.symbol();
out.push_str("\t.p2align\t4, 0x90\n");
let _ = writeln!(out, "\t.globl\t{symbol}{name}");
match self {
Directives::Elf => {
let _ = writeln!(out, "\t.type\t{name}, @function");
}
Directives::Coff => {
let _ = writeln!(out, "\t.def\t{name}\n\t.scl\t2\n\t.type\t32\n\t.endef");
}
Directives::MachO => {}
}
let _ = writeln!(out, "{symbol}{name}:");
}
pub fn section(self, out: &mut String, place: &Place) {
match (self, place) {
(Directives::Elf | Directives::Coff, Place::Written | Place::Merged) => {
out.push_str("\t.data\n");
}
(Directives::Elf | Directives::Coff, Place::Zero) => out.push_str("\t.bss\n"),
(Directives::Elf, Place::ReadOnly) => out.push_str("\t.section\t.rodata\n"),
(Directives::Coff, Place::ReadOnly) => out.push_str("\t.section\t.rdata,\"dr\"\n"),
(Directives::Elf, Place::Named(name)) => {
let _ = writeln!(out, "\t.section\t{name},\"aw\",@progbits");
}
(Directives::Coff, Place::Named(name)) => {
let _ = writeln!(out, "\t.section\t{name},\"dw\"");
}
(Directives::MachO, Place::ReadOnly) => out.push_str("\t.section\t__TEXT,__const\n"),
(Directives::MachO, Place::Named(name)) => {
let _ = writeln!(out, "\t.section\t{name}");
}
(Directives::MachO, _) => out.push_str("\t.section\t__DATA,__data\n"),
}
}
pub fn variable(self, out: &mut String, var: &Variable) -> bool {
let symbol = self.symbol();
let align = var.align.max(1).trailing_zeros();
match (self, &var.place) {
(_, Place::Merged) => {
let comm = if var.binding == Binding::Local { ".lcomm" } else { ".comm" };
let name = &var.name;
let _ = writeln!(out, "\t{comm}\t{symbol}{name},{},{}", var.size, var.align);
return false;
}
(Directives::MachO, Place::Zero) => {
let name = &var.name;
let _ =
writeln!(out, "\t.zerofill\t__DATA,__bss,{symbol}{name},{},{align}", var.size);
return false;
}
_ => {}
}
self.section(out, &var.place);
match var.binding {
Binding::Global => {
let _ = writeln!(out, "\t.globl\t{symbol}{}", var.name);
}
Binding::Weak => {
let _ = writeln!(out, "\t.weak\t{symbol}{}", var.name);
}
Binding::Local => {}
}
let _ = writeln!(out, "\t.p2align\t{align}");
if self == Directives::Elf {
let _ = writeln!(out, "\t.type\t{}, @object", var.name);
}
let _ = writeln!(out, "{symbol}{}:", var.name);
true
}
pub fn close(self, out: &mut String, name: &str) {
if self == Directives::Elf {
let _ = writeln!(out, "\t.size\t{name}, .-{name}");
}
}
pub fn end(self, out: &mut String) {
match self {
Directives::Elf => out.push_str("\t.section\t.note.GNU-stack,\"\",@progbits\n"),
Directives::MachO => out.push_str("\t.subsections_via_symbols\n"),
Directives::Coff => {}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_mach_o_symbol_is_the_c_name_with_an_underscore_in_front_of_it() {
let mut out = String::new();
Directives::MachO.open(&mut out, "main");
assert!(out.contains("\t.globl\t_main\n"), "{out}");
assert!(out.contains("\n_main:\n"), "{out}");
assert!(!out.contains(".type"), "{out}");
let mut close = String::new();
Directives::MachO.close(&mut close, "main");
assert_eq!(close, "");
}
#[test]
fn an_elf_function_says_what_it_is_and_how_long_it_is() {
let mut out = String::new();
Directives::Elf.open(&mut out, "main");
Directives::Elf.close(&mut out, "main");
assert!(out.contains("\t.type\tmain, @function\n"), "{out}");
assert!(out.contains("\t.size\tmain, .-main\n"), "{out}");
}
#[test]
fn an_elf_file_says_the_stack_is_not_executable() {
let mut out = String::new();
Directives::Elf.end(&mut out);
assert!(out.contains(".note.GNU-stack"), "{out}");
}
#[test]
fn every_object_format_has_directives() {
for format in [ObjectFormat::Elf, ObjectFormat::MachO, ObjectFormat::Coff] {
let directives = Directives::of(format);
assert!(directives.text().starts_with('\t'));
let mut out = String::new();
directives.open(&mut out, "f");
directives.close(&mut out, "f");
directives.end(&mut out);
assert!(out.ends_with('\n'), "{format:?} left a line unfinished");
}
}
}