use std::collections::{BTreeMap, HashMap};
use rucc_object::{
Array, Assembled, Binding, Held, Name, Part, Reference, Reloc, Shape, Sort, Visibility,
};
use crate::instruction::Sort as Reach;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Trouble {
pub line: usize,
pub why: String,
}
impl std::fmt::Display for Trouble {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: {}", self.line, self.why)
}
}
impl std::error::Error for Trouble {}
pub fn read(text: &str) -> Result<Assembled, Trouble> {
let mut reader = Reader::default();
reader.run(text)?;
reader.finish()
}
#[derive(Debug, Clone)]
struct Sym {
name: String,
at: Held,
size: u64,
sort: Sort,
binding: Binding,
visibility: Visibility,
numbered: bool,
}
#[derive(Debug, Clone)]
struct Fixup {
part: usize,
at: u64,
width: u8,
sum: Sum,
reach: Reach,
line: usize,
}
#[derive(Debug, Default)]
struct Reader {
parts: Vec<Part>,
named: HashMap<String, usize>,
here: usize,
stack: Vec<usize>,
before: Option<usize>,
syms: Vec<Sym>,
known: HashMap<String, usize>,
counts: HashMap<String, usize>,
labelled: std::collections::HashSet<usize>,
fixups: Vec<Fixup>,
sets: Vec<(usize, Sum, usize)>,
sizes: Vec<(usize, Sum, usize)>,
current: HashMap<String, String>,
relocated: std::collections::HashSet<usize>,
files: Vec<String>,
line: usize,
}
impl Reader {
fn run(&mut self, text: &str) -> Result<(), Trouble> {
self.section(".text", Shape::of(".text"));
let mut commenting = false;
for (index, raw) in text.lines().enumerate() {
self.line = index + 1;
let line = self.strip(raw, &mut commenting)?;
for statement in split(&line, ';') {
self.statement(statement.trim())?;
}
}
if commenting {
return Err(self.bad("a block comment was opened and never closed"));
}
Ok(())
}
fn strip(&self, raw: &str, commenting: &mut bool) -> Result<String, Trouble> {
let mut out = String::with_capacity(raw.len());
let bytes = raw.as_bytes();
let mut i = 0;
let mut quote = None;
while i < bytes.len() {
let rest = &raw[i..];
if *commenting {
if let Some(end) = rest.find("*/") {
*commenting = false;
out.push(' ');
i += end + 2;
} else {
return Ok(out);
}
continue;
}
let ch = bytes[i] as char;
if let Some(mark) = quote {
out.push(ch);
if ch == '\\' && i + 1 < bytes.len() {
out.push(bytes[i + 1] as char);
i += 2;
continue;
}
if ch == mark {
quote = None;
}
i += 1;
continue;
}
if ch == '"' {
quote = Some('"');
out.push(ch);
i += 1;
continue;
}
if rest.starts_with("/*") {
*commenting = true;
i += 2;
continue;
}
if rest.starts_with("//") || ch == '#' {
return Ok(out);
}
out.push(ch);
i += 1;
}
if quote.is_some() {
return Err(self.bad("a string was opened and the line ended before it closed"));
}
Ok(out)
}
fn statement(&mut self, mut text: &str) -> Result<(), Trouble> {
loop {
text = text.trim_start();
let Some(name) = labelled(text) else { break };
self.label(&name)?;
text = &text[name.len() + 1..];
}
let text = text.trim();
if text.is_empty() {
return Ok(());
}
let (word, rest) = match text.find(char::is_whitespace) {
Some(cut) => (&text[..cut], text[cut..].trim()),
None => (text, ""),
};
if let Some((name, what)) = assigned(text) {
return self.assign(name, what);
}
if let Some(directive) = word.strip_prefix('.') {
return self.directive(directive, rest);
}
if let Some((word, rest)) = repeated(word, rest) {
return self.instruction(&word, rest);
}
self.instruction(word, rest)
}
fn instruction(&mut self, word: &str, rest: &str) -> Result<(), Trouble> {
let args = if rest.is_empty() { Vec::new() } else { split(rest, ',') };
let written = crate::instruction::one(word, &args).map_err(|why| self.bad(&why))?;
let part = self.here;
let at = self.at();
self.put(&written.bytes)?;
let end = at + written.bytes.len() as u64;
for hole in written.holes {
let here = (part, at as i64);
let sum = if hole.sort == Reach::Value {
self.expression_at(&hole.name, here)?
} else {
let what = if hole.name == "." {
What::Here { part, at: here.1 }
} else {
let name = self.named(&hole.name)?;
self.sym(&name);
What::Symbol(name)
};
Sum {
constant: hole.addend,
terms: vec![
Term { coeff: 1, what },
Term { coeff: -1, what: What::Here { part, at: end as i64 } },
],
}
};
self.fixups.push(Fixup {
part,
at: at + hole.at as u64,
width: hole.width,
sum,
reach: hole.sort,
line: self.line,
});
}
Ok(())
}
fn label(&mut self, name: &str) -> Result<(), Trouble> {
let at = self.at();
let part = self.here;
let numbered = name.bytes().all(|byte| byte.is_ascii_digit());
let held = if numbered {
let count = self.counts.entry(name.to_owned()).or_insert(0);
*count += 1;
counted(name, *count)
} else {
name.to_owned()
};
let sym = self.sym(&held);
if self.syms[sym].at != Held::Undefined {
let what = format!("'{name}' is defined twice");
return Err(self.bad(&what));
}
self.syms[sym].at = Held::In { part, offset: at };
self.labelled.insert(part);
Ok(())
}
fn numbered(&self, word: &str) -> Result<Option<String>, Trouble> {
let Some(number) = word.strip_suffix(['b', 'f']) else {
return Ok(None);
};
if number.is_empty() || !number.bytes().all(|byte| byte.is_ascii_digit()) {
return Ok(None);
}
let count = self.counts.get(number).copied().unwrap_or(0);
if word.ends_with('b') {
if count == 0 {
let what =
format!("'{word}' goes back to a '{number}:' and there is none above it");
return Err(self.bad(&what));
}
return Ok(Some(counted(number, count)));
}
Ok(Some(counted(number, count + 1)))
}
fn named(&self, word: &str) -> Result<String, Trouble> {
if let Some(place) = self.numbered(word)? {
return Ok(place);
}
Ok(self.current.get(word).cloned().unwrap_or_else(|| word.to_owned()))
}
fn assign(&mut self, name: &str, what: &str) -> Result<(), Trouble> {
let sum = self.expression(what)?;
let held = match self.current.get(name) {
Some(_) => format!("{name}\u{1}={}", self.syms.len()),
None => name.to_owned(),
};
let sym = self.sym(&held);
if self.syms[sym].at != Held::Undefined {
let what = format!("'{name}' is defined twice");
return Err(self.bad(&what));
}
self.current.insert(name.to_owned(), held);
self.sets.push((sym, sum, self.line));
Ok(())
}
#[allow(clippy::too_many_lines)]
fn directive(&mut self, word: &str, rest: &str) -> Result<(), Trouble> {
let args = split(rest, ',');
match word {
"text" | "data" | "bss" | "rodata" => {
self.plain(word, rest)?;
}
"section" => self.section_directive(&args)?,
"pushsection" => {
self.stack.push(self.here);
self.section_directive(&args)?;
}
"popsection" => {
let Some(back) = self.stack.pop() else {
return Err(self.bad(".popsection with nothing pushed"));
};
self.go(back);
}
"previous" => {
let Some(back) = self.before else {
return Err(self.bad(".previous with no section before this one"));
};
self.go(back);
}
"byte" => self.data(&args, 1)?,
"short" | "word" | "hword" | "value" | "2byte" => self.data(&args, 2)?,
"long" | "int" | "4byte" => self.data(&args, 4)?,
"quad" | "8byte" => self.data(&args, 8)?,
"ascii" => self.text_bytes(&args, false)?,
"asciz" | "string" => self.text_bytes(&args, true)?,
"space" | "skip" | "zero" => {
if args.is_empty() || args.len() > 2 {
return Err(self.bad(&format!(".{word} wants a size and an optional fill")));
}
let size = self.number(&args[0])?;
let size = self.count(size)?;
let fill = match args.get(1) {
Some(arg) => self.byte(arg)?,
None => 0,
};
self.pad(size, fill)?;
}
"fill" => {
if args.is_empty() || args.len() > 3 {
return Err(self.bad(".fill wants a count and an optional width and value"));
}
let count = self.number(&args[0])?;
let count = self.count(count)?;
let width = match args.get(1) {
Some(arg) => {
let width = self.number(arg)?;
self.count(width)?
}
None => 1,
};
let value = match args.get(2) {
Some(arg) => self.number(arg)?,
None => 0,
};
if width > 8 {
return Err(self.bad(".fill of items wider than eight bytes is not written"));
}
let one = value.to_le_bytes();
for _ in 0..count {
self.put(&one[..width as usize])?;
}
}
"align" | "balign" | "p2align" => self.align(word, &args)?,
"org" => {
let Some(first) = args.first() else {
return Err(self.bad(".org with nothing after it"));
};
let to = self.number(first)?;
let to = self.count(to)?;
let fill = match args.get(1) {
Some(arg) => self.byte(arg)?,
None => 0,
};
let at = self.at();
if to < at {
let what = format!(".org back to {to} from {at}, which would overwrite bytes");
return Err(self.bad(&what));
}
self.pad(to - at, fill)?;
}
"globl" | "global" => self.bind(&args, Binding::Global)?,
"weak" => self.bind(&args, Binding::Weak)?,
"local" => self.bind(&args, Binding::Local)?,
"hidden" => self.sight(&args, Visibility::Hidden)?,
"protected" => self.sight(&args, Visibility::Protected)?,
"internal" => self.sight(&args, Visibility::Hidden)?,
"type" => self.type_directive(&args)?,
"err" | "error" => {
let what = unquoted(args.first().map_or("", |arg| arg.trim()));
return Err(self.bad(&format!("the file says so itself: {what}")));
}
"size" => {
let [name, what] = self.two(&args, ".size")?;
let sum = self.expression(&what)?;
let sym = self.sym(&name);
self.sizes.push((sym, sum, self.line));
}
"set" | "equ" | "equiv" => {
let [name, what] = self.two(&args, &format!(".{word}"))?;
self.assign(&name, &what)?;
}
"comm" | "lcomm" => self.common(&args, word == "lcomm")?,
"file" => {
let what = args.first().map_or("", |arg| arg.trim());
if what.starts_with('"') {
self.files.push(unquoted(what));
}
}
"ident" | "loc" | "loc_mark_labels" | "version" | "arch" | "code64" | "att_syntax"
| "intel_syntax" | "warning" => {}
_ if word.starts_with("cfi_") => {}
_ => {
let what = format!(
"'.{word}' is a directive this compiler does not know, so nothing was written \
for it"
);
return Err(self.bad(&what));
}
}
Ok(())
}
fn plain(&mut self, word: &str, rest: &str) -> Result<(), Trouble> {
if !rest.trim().is_empty() && rest.trim() != "0" {
let what =
format!("'.{word} {}' is a subsection, which is not written yet", rest.trim());
return Err(self.bad(&what));
}
let name = format!(".{word}");
let shape = Shape::of(&name);
self.section(&name, shape);
Ok(())
}
fn section_directive(&mut self, args: &[String]) -> Result<(), Trouble> {
let Some(name) = args.first() else {
return Err(self.bad(".section with no name"));
};
let name = unquoted(name.trim());
if name.is_empty() {
return Err(self.bad(".section with no name"));
}
let mut shape = Shape::of(&name);
if let Some(flags) = args.get(1) {
let letters = unquoted(flags.trim());
shape = Shape { bits: true, ..Shape::default() };
for letter in letters.chars() {
match letter {
'a' => shape.alloc = true,
'w' => shape.write = true,
'x' => shape.exec = true,
'T' => shape.thread = true,
'M' | 'S' | 'G' | 'o' | 'e' | 'R' | 'd' => {}
_ => {
let what = format!("'{letter}' is not a section flag this compiler knows");
return Err(self.bad(&what));
}
}
}
}
if let Some(kind) = args.get(2) {
let kind = kind.trim().trim_start_matches(['@', '%']);
let kind = unquoted(kind);
match kind.as_str() {
"progbits" => shape.bits = true,
"nobits" => shape.bits = false,
"init_array" => shape.array = Some(Array::Init),
"fini_array" => shape.array = Some(Array::Fini),
"preinit_array" => shape.array = Some(Array::Preinit),
"note" => shape.bits = true,
_ => {
let what = format!("'{kind}' is not a section type this compiler writes");
return Err(self.bad(&what));
}
}
}
self.section(&name, shape);
Ok(())
}
fn section(&mut self, name: &str, shape: Shape) {
if let Some(&at) = self.named.get(name) {
self.go(at);
return;
}
let at = self.parts.len();
self.parts.push(Part {
name: name.to_owned(),
bytes: Vec::new(),
size: 0,
align: 1,
shape,
relocs: Vec::new(),
});
self.named.insert(name.to_owned(), at);
self.go(at);
}
fn go(&mut self, at: usize) {
if at != self.here {
self.before = Some(self.here);
self.here = at;
}
}
fn data(&mut self, args: &[String], width: u8) -> Result<(), Trouble> {
if args.is_empty() {
return Err(self.bad("a data directive with nothing after it"));
}
for arg in args {
let sum = self.expression(arg)?;
let at = self.at();
if let Some(value) = sum.flat() {
self.put(&value.to_le_bytes()[..width as usize])?;
continue;
}
let part = self.here;
if !self.parts[part].shape.bits {
let what = format!(
"'{}' holds no bytes and this asks the linker to write some into it",
self.parts[part].name
);
return Err(self.bad(&what));
}
self.put(&vec![0u8; width as usize])?;
self.fixups.push(Fixup { part, at, width, sum, reach: Reach::Near, line: self.line });
}
Ok(())
}
fn text_bytes(&mut self, args: &[String], terminated: bool) -> Result<(), Trouble> {
for arg in args {
let mut bytes = self.string(arg.trim())?;
if terminated {
bytes.push(0);
}
self.put(&bytes)?;
}
Ok(())
}
fn align(&mut self, word: &str, args: &[String]) -> Result<(), Trouble> {
let Some(head) = args.first() else {
return Err(self.bad(&format!(".{word} with nothing after it")));
};
let first = self.number(head)?;
let first = self.count(first)?;
let boundary = if word == "p2align" {
if first > 31 {
return Err(self.bad(".p2align of more than two gigabytes"));
}
1u64 << first
} else {
first
};
if boundary == 0 || !boundary.is_power_of_two() {
let what = format!("an alignment of {boundary}, which is not a power of two");
return Err(self.bad(&what));
}
let default = if self.parts[self.here].shape.exec { 0x90 } else { 0 };
let fill = match args.get(1) {
Some(arg) if !arg.trim().is_empty() => self.byte(arg)?,
_ => default,
};
let at = self.at();
let over = at % boundary;
let need = if over == 0 { 0 } else { boundary - over };
if let Some(most) = args.get(2).filter(|arg| !arg.trim().is_empty()) {
let most = self.number(&most.clone())?;
if need > self.count(most)? {
return Ok(());
}
}
let part = &mut self.parts[self.here];
part.align = part.align.max(boundary);
self.pad(need, fill)
}
fn bind(&mut self, args: &[String], binding: Binding) -> Result<(), Trouble> {
for arg in args {
let sym = self.sym(arg.trim());
self.syms[sym].binding = binding;
}
Ok(())
}
fn sight(&mut self, args: &[String], visibility: Visibility) -> Result<(), Trouble> {
for arg in args {
let sym = self.sym(arg.trim());
self.syms[sym].visibility = visibility;
}
Ok(())
}
fn type_directive(&mut self, args: &[String]) -> Result<(), Trouble> {
let [name, what] = self.two(args, ".type")?;
let what = unquoted(what.trim().trim_start_matches(['@', '%']));
let sort = match what.trim_start_matches("STT_").to_ascii_lowercase().as_str() {
"func" | "function" => Sort::Func,
"object" | "gnu_unique_object" => Sort::Object,
"tls_object" | "tls" => Sort::Thread,
"notype" | "" => Sort::Untyped,
other => {
let what = format!("'{other}' is not a symbol type this compiler writes");
return Err(self.bad(&what));
}
};
let sym = self.sym(name.trim());
self.syms[sym].sort = sort;
Ok(())
}
fn common(&mut self, args: &[String], local: bool) -> Result<(), Trouble> {
if !(2..=3).contains(&args.len()) {
return Err(
self.bad("a common directive wants a name, a size and an optional alignment")
);
}
let name = args[0].trim().to_owned();
let size = self.number(&args[1])?;
let size = self.count(size)?;
let align = match args.get(2) {
Some(arg) => {
let align = self.number(&arg.clone())?;
self.count(align)?.max(1)
}
None => size.next_power_of_two().clamp(1, 16),
};
if !align.is_power_of_two() {
let what = format!("an alignment of {align}, which is not a power of two");
return Err(self.bad(&what));
}
let sym = self.sym(&name);
self.syms[sym].sort = Sort::Object;
if local {
let was = self.here;
self.section(".bss", Shape::of(".bss"));
let part = &mut self.parts[self.here];
part.align = part.align.max(align);
let over = part.size % align;
if over != 0 {
part.size += align - over;
}
let offset = self.parts[self.here].size;
self.parts[self.here].size += size;
let at = self.here;
self.syms[sym].at = Held::In { part: at, offset };
self.syms[sym].size = size;
self.syms[sym].binding = Binding::Local;
self.go(was);
} else {
self.syms[sym].at = Held::Common { size, align };
self.syms[sym].size = size;
self.syms[sym].binding = Binding::Global;
}
Ok(())
}
fn at(&self) -> u64 {
let part = &self.parts[self.here];
if part.shape.bits { part.bytes.len() as u64 } else { part.size }
}
fn put(&mut self, bytes: &[u8]) -> Result<(), Trouble> {
let part = &mut self.parts[self.here];
if !part.shape.bits {
if bytes.iter().all(|byte| *byte == 0) {
part.size += bytes.len() as u64;
return Ok(());
}
let what = format!("'{}' holds no bytes and this puts some in it", part.name);
return Err(Trouble { line: self.line, why: what });
}
part.bytes.extend_from_slice(bytes);
part.size = part.bytes.len() as u64;
Ok(())
}
fn pad(&mut self, count: u64, fill: u8) -> Result<(), Trouble> {
let part = &mut self.parts[self.here];
if !part.shape.bits {
part.size += count;
return Ok(());
}
part.bytes.resize(part.bytes.len() + usize::try_from(count).unwrap_or(usize::MAX), fill);
part.size = part.bytes.len() as u64;
Ok(())
}
fn sym(&mut self, name: &str) -> usize {
if let Some(&at) = self.known.get(name) {
return at;
}
let at = self.syms.len();
self.syms.push(Sym {
name: name.to_owned(),
at: Held::Undefined,
size: 0,
sort: Sort::Untyped,
binding: Binding::Local,
visibility: Visibility::Default,
numbered: name.contains('\u{1}'),
});
self.known.insert(name.to_owned(), at);
at
}
fn two(&self, args: &[String], what: &str) -> Result<[String; 2], Trouble> {
if args.len() != 2 {
let why = format!("{what} wants two operands and was given {}", args.len());
return Err(Trouble { line: self.line, why });
}
Ok([args[0].trim().to_owned(), args[1].trim().to_owned()])
}
fn number(&mut self, text: &str) -> Result<i64, Trouble> {
let sum = self.expression(text)?;
sum.flat().ok_or_else(|| Trouble {
line: self.line,
why: format!("'{}' has to be a number here and it names something", text.trim()),
})
}
fn byte(&mut self, text: &str) -> Result<u8, Trouble> {
let value = self.number(text)?;
u8::try_from(value & 0xff).map_err(|_| Trouble {
line: self.line,
why: format!("{value} does not fit in a byte"),
})
}
fn count(&self, value: i64) -> Result<u64, Trouble> {
u64::try_from(value).map_err(|_| Trouble {
line: self.line,
why: format!("{value} is negative and this is a length"),
})
}
fn expression(&mut self, text: &str) -> Result<Sum, Trouble> {
self.expression_at(text, (self.here, self.at() as i64))
}
fn expression_at(&mut self, text: &str, here: (usize, i64)) -> Result<Sum, Trouble> {
let mut parser = Parser { text: text.trim(), at: 0, here };
let mut sum = parser.whole().map_err(|why| Trouble { line: self.line, why })?;
for term in &mut sum.terms {
if let What::Symbol(name) = &term.what {
let name = self.named(name)?;
self.sym(&name);
term.what = What::Symbol(name);
}
}
Ok(sum)
}
fn bad(&self, why: &str) -> Trouble {
Trouble { line: self.line, why: why.to_owned() }
}
fn finish(mut self) -> Result<Assembled, Trouble> {
self.resolve_sets()?;
self.resolve_sizes()?;
self.resolve_fixups()?;
let keep: Vec<bool> = self
.parts
.iter()
.enumerate()
.map(|(at, part)| {
part.size > 0 || !part.relocs.is_empty() || self.labelled.contains(&at)
})
.collect();
let mut moved = vec![0usize; self.parts.len()];
let mut parts = Vec::with_capacity(self.parts.len());
for (at, part) in self.parts.into_iter().enumerate() {
if keep[at] {
moved[at] = parts.len();
parts.push(part);
}
}
let mut names = Vec::with_capacity(self.syms.len() + self.files.len());
for file in self.files {
names.push(Name {
name: file,
at: Held::Absolute(0),
size: 0,
sort: Sort::File,
binding: Binding::Local,
visibility: Visibility::Default,
});
}
for (index, sym) in self.syms.into_iter().enumerate() {
if sym.numbered && !self.relocated.contains(&index) {
continue;
}
let at = match sym.at {
Held::In { part, offset } => Held::In { part: moved[part], offset },
other => other,
};
let binding = match (at, sym.binding) {
(Held::Undefined, Binding::Local) => Binding::Global,
(_, binding) => binding,
};
names.push(Name {
name: sym.name,
at,
size: sym.size,
sort: sym.sort,
binding,
visibility: sym.visibility,
});
}
Ok(Assembled { parts, names })
}
fn resolve_sets(&mut self) -> Result<(), Trouble> {
while !self.sets.is_empty() {
let mut done = Vec::new();
for (at, (sym, sum, line)) in self.sets.iter().enumerate() {
if let Ok(residue) = self.reduce(sum) {
done.push((at, *sym, self.settled(&residue, *line)?));
}
}
if done.is_empty() {
let (sym, _, line) = &self.sets[0];
let why = format!(
"'{}' is set to something that is set to it, so neither has a value",
self.syms[*sym].name
);
return Err(Trouble { line: *line, why });
}
for (_, sym, held) in &done {
self.syms[*sym].at = *held;
}
for (at, _, _) in done.iter().rev() {
self.sets.remove(*at);
}
}
Ok(())
}
fn settled(&self, residue: &Residue, line: usize) -> Result<Held, Trouble> {
match residue.left.as_slice() {
[] => Ok(Held::Absolute(residue.constant as u64)),
[Left { coeff: 1, at: Some((part, offset)), .. }] => {
Ok(Held::In { part: *part, offset: (*offset + residue.constant) as u64 })
}
_ => Err(Trouble {
line,
why: "a set to something that is neither a number nor a place in this file"
.to_owned(),
}),
}
}
fn resolve_sizes(&mut self) -> Result<(), Trouble> {
for (sym, sum, line) in std::mem::take(&mut self.sizes) {
let residue = self.reduce(&sum).map_err(|why| Trouble { line, why })?;
if !residue.left.is_empty() {
let why = format!(
"the size of '{}' is not a number, and a size has to be one",
self.syms[sym].name
);
return Err(Trouble { line, why });
}
let size = self.count(residue.constant).map_err(|_| Trouble {
line,
why: format!("'{}' is given a negative size", self.syms[sym].name),
})?;
self.syms[sym].size = size;
}
Ok(())
}
fn resolve_fixups(&mut self) -> Result<(), Trouble> {
for fixup in std::mem::take(&mut self.fixups) {
let line = fixup.line;
let bad = |why: String| Trouble { line, why };
if matches!(fixup.reach, Reach::Table | Reach::Thread) {
let [
Term { coeff: 1, what: What::Symbol(name) },
Term { coeff: -1, what: What::Here { at: end, .. } },
] = fixup.sum.terms.as_slice()
else {
return Err(bad(
"a reach through the global offset table in something other than an \
instruction, which is not an expression this compiler writes"
.to_owned(),
));
};
let kind =
if fixup.reach == Reach::Table { Reference::Got } else { Reference::Thread };
self.parts[fixup.part].relocs.push(Reloc {
at: fixup.at as usize,
symbol: name.clone(),
kind,
addend: fixup.sum.constant + fixup.at as i64 - end,
after: (end - fixup.at as i64 - 4).max(0) as u8,
});
continue;
}
let residue = self.reduce(&fixup.sum).map_err(|why| Trouble { line, why })?;
if fixup.reach == Reach::Value && !residue.left.is_empty() {
return Err(bad(
"a number in an instruction that names something outside this section, \
which wants a relocation this compiler does not write yet"
.to_owned(),
));
}
let (symbol, kind, addend, after) = match residue.left.as_slice() {
[] => {
let width = fixup.width as usize;
let room = 8 * width as u32;
let low = -(1i64 << (room - 1));
let high = if fixup.reach == Reach::Branch {
(1i64 << (room - 1)) - 1
} else {
(1i64 << room) - 1
};
if width < 8 && (residue.constant < low || residue.constant > high) {
return Err(bad(format!(
"{} written into {width} bytes, which does not reach it",
residue.constant
)));
}
let bytes = residue.constant.to_le_bytes();
let at = fixup.at as usize;
let part = &mut self.parts[fixup.part];
part.bytes[at..at + width].copy_from_slice(&bytes[..width]);
continue;
}
[Left { coeff: 1, what: What::Symbol(name), .. }] => {
let kind = Reference::Address { bytes: fixup.width };
(name.clone(), kind, residue.constant, 0)
}
[
Left { coeff: 1, what: What::Symbol(name), .. },
Left { coeff: -1, at: Some((part, offset)), .. },
]
| [
Left { coeff: -1, at: Some((part, offset)), .. },
Left { coeff: 1, what: What::Symbol(name), .. },
] => {
if *part != fixup.part {
return Err(bad(
"a distance that is subtracted from somewhere in another section"
.to_owned(),
));
}
if fixup.width != 4 {
return Err(bad(format!(
"a distance written into {} bytes, and four is the only width a \
relocation says one at",
fixup.width
)));
}
let addend = residue.constant + fixup.at as i64 - offset;
let kind = if fixup.reach == Reach::Branch {
Reference::Call
} else {
Reference::Data
};
let after = (offset - fixup.at as i64 - 4).max(0);
(name.clone(), kind, addend, after as u8)
}
[Left { coeff: 1, what: What::Here { .. }, .. }] => {
return Err(bad(
"the address of these bytes themselves, which has no symbol to be \
relocated against"
.to_owned(),
));
}
_ => {
return Err(bad(
"an expression that does not come out as a number, an address, or a \
distance, and those are what a relocation can say"
.to_owned(),
));
}
};
if let Some(&sym) = self.known.get(&symbol) {
if self.syms[sym].numbered && self.syms[sym].at != Held::Undefined {
self.relocated.insert(sym);
} else if self.syms[sym].numbered {
let number = symbol.split('\u{1}').next().unwrap_or(&symbol);
return Err(bad(format!(
"'{number}f' goes on to a '{number}:' and there is none below it"
)));
}
}
if matches!(kind, Reference::Address { bytes } if bytes != 4 && bytes != 8) {
return Err(bad(format!(
"the address of '{symbol}' written into {} bytes, and this machine relocates \
an address at four or eight",
fixup.width
)));
}
self.parts[fixup.part].relocs.push(Reloc {
at: fixup.at as usize,
symbol,
kind,
addend,
after,
});
}
Ok(())
}
fn reduce(&self, sum: &Sum) -> Result<Residue, String> {
let mut constant = sum.constant;
let mut placed: BTreeMap<usize, Vec<(i64, What, i64)>> = BTreeMap::new();
let mut outside: Vec<(i64, String)> = Vec::new();
for term in &sum.terms {
match &term.what {
What::Here { part, at } => {
placed.entry(*part).or_default().push((term.coeff, term.what.clone(), *at));
}
What::Symbol(name) => {
let Some(&at) = self.known.get(name) else {
return Err(format!("'{name}' is named and never said"));
};
match self.syms[at].at {
Held::Absolute(value) => constant += term.coeff * value as i64,
Held::In { part, offset } => placed.entry(part).or_default().push((
term.coeff,
term.what.clone(),
offset as i64,
)),
Held::Undefined | Held::Common { .. } => {
if !self.sets.iter().any(|(sym, _, _)| *sym == at) {
outside.push((term.coeff, name.clone()));
} else {
return Err(format!("'{name}' is not worked out yet"));
}
}
}
}
}
}
let mut left: Vec<Left> = Vec::new();
for (part, terms) in placed {
let (_, chosen, base) = terms[0].clone();
let mut net = 0;
for (coeff, _, offset) in &terms {
net += coeff;
constant += coeff * (offset - base);
}
if net != 0 {
left.push(Left { coeff: net, what: chosen, at: Some((part, base)) });
}
}
let mut together: BTreeMap<String, i64> = BTreeMap::new();
for (coeff, name) in outside {
*together.entry(name).or_default() += coeff;
}
for (name, coeff) in together {
if coeff != 0 {
left.push(Left { coeff, what: What::Symbol(name), at: None });
}
}
Ok(Residue { constant, left })
}
}
#[derive(Debug, Clone)]
struct Residue {
constant: i64,
left: Vec<Left>,
}
#[derive(Debug, Clone)]
struct Left {
coeff: i64,
what: What,
at: Option<(usize, i64)>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
struct Sum {
constant: i64,
terms: Vec<Term>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Term {
coeff: i64,
what: What,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum What {
Symbol(String),
Here { part: usize, at: i64 },
}
impl Sum {
fn flat(&self) -> Option<i64> {
self.terms.is_empty().then_some(self.constant)
}
fn of(what: What) -> Sum {
Sum { constant: 0, terms: vec![Term { coeff: 1, what }] }
}
fn just(value: i64) -> Sum {
Sum { constant: value, terms: Vec::new() }
}
fn plus(mut self, other: Sum) -> Sum {
self.constant = self.constant.wrapping_add(other.constant);
self.terms.extend(other.terms);
self
}
fn minus(self) -> Sum {
Sum {
constant: self.constant.wrapping_neg(),
terms: self
.terms
.into_iter()
.map(|term| Term { coeff: term.coeff.wrapping_neg(), what: term.what })
.collect(),
}
}
fn times(self, factor: i64) -> Sum {
Sum {
constant: self.constant.wrapping_mul(factor),
terms: self
.terms
.into_iter()
.map(|term| Term { coeff: term.coeff.wrapping_mul(factor), what: term.what })
.collect(),
}
}
}
struct Parser<'a> {
text: &'a str,
at: usize,
here: (usize, i64),
}
impl Parser<'_> {
fn whole(&mut self) -> Result<Sum, String> {
let sum = self.bitwise()?;
self.space();
if self.at < self.text.len() {
return Err(format!(
"'{}' is left over at the end of an expression",
&self.text[self.at..]
));
}
Ok(sum)
}
fn bitwise(&mut self) -> Result<Sum, String> {
let mut left = self.shift()?;
loop {
self.space();
let Some(op) = self.one_of(&["|", "^", "&"]) else { return Ok(left) };
let right = self.shift()?;
left = self.arithmetic(left, right, op)?;
}
}
fn shift(&mut self) -> Result<Sum, String> {
let mut left = self.sum()?;
loop {
self.space();
let Some(op) = self.one_of(&["<<", ">>"]) else { return Ok(left) };
let right = self.sum()?;
left = self.arithmetic(left, right, op)?;
}
}
fn sum(&mut self) -> Result<Sum, String> {
let mut left = self.product()?;
loop {
self.space();
let Some(op) = self.one_of(&["+", "-"]) else { return Ok(left) };
let right = self.product()?;
left = if op == "+" { left.plus(right) } else { left.plus(right.minus()) };
}
}
fn product(&mut self) -> Result<Sum, String> {
let mut left = self.unary()?;
loop {
self.space();
let Some(op) = self.one_of(&["*", "/", "%"]) else { return Ok(left) };
let right = self.unary()?;
left = match (op, left.flat(), right.flat()) {
("*", _, Some(factor)) => left.times(factor),
("*", Some(factor), _) => right.times(factor),
(_, Some(a), Some(b)) => Sum::just(self.arithmetic_number(a, b, op)?),
_ => return Err(format!("'{op}' of something that names a symbol")),
};
}
}
fn unary(&mut self) -> Result<Sum, String> {
self.space();
if self.eat("-") {
return Ok(self.unary()?.minus());
}
if self.eat("+") {
return self.unary();
}
if self.eat("~") {
let inner = self.unary()?;
let value = inner
.flat()
.ok_or_else(|| "a complement of something that names a symbol".to_owned())?;
return Ok(Sum::just(!value));
}
if self.eat("!") {
let inner = self.unary()?;
let value = inner
.flat()
.ok_or_else(|| "a negation of something that names a symbol".to_owned())?;
return Ok(Sum::just(i64::from(value == 0)));
}
self.primary()
}
fn primary(&mut self) -> Result<Sum, String> {
self.space();
let rest = &self.text[self.at..];
if rest.is_empty() {
return Err("an expression that stops before it says anything".to_owned());
}
if self.eat("(") {
let inner = self.bitwise()?;
self.space();
if !self.eat(")") {
return Err("a bracket that was opened and never closed".to_owned());
}
return Ok(inner);
}
let first = rest.as_bytes()[0];
if first == b'\'' {
return self.character();
}
if first.is_ascii_digit() {
let end = rest.find(|ch: char| !ch.is_ascii_digit()).unwrap_or(rest.len());
let bytes = rest.as_bytes();
if matches!(bytes.get(end), Some(b'b' | b'f'))
&& !bytes.get(end + 1).is_some_and(|byte| carries_on(*byte))
{
self.at += end + 1;
return Ok(Sum::of(What::Symbol(rest[..=end].to_owned())));
}
return self.digits();
}
if starts(first) {
let name = self.word();
if name == "." {
let (part, at) = self.here;
return Ok(Sum::of(What::Here { part, at }));
}
if self.text[self.at..].starts_with('@') {
return Err(format!(
"'{name}@' asks for a relocation only an instruction can carry"
));
}
return Ok(Sum::of(What::Symbol(name)));
}
Err(format!("'{rest}' is not the start of an expression"))
}
fn digits(&mut self) -> Result<Sum, String> {
let rest = &self.text[self.at..];
let (radix, skip) = if rest.starts_with("0x") || rest.starts_with("0X") {
(16, 2)
} else if rest.starts_with("0b") || rest.starts_with("0B") {
(2, 2)
} else if rest.len() > 1 && rest.starts_with('0') {
(8, 1)
} else {
(10, 0)
};
let body = &rest[skip..];
let end = body.find(|ch: char| !ch.is_digit(radix) && ch != '_').unwrap_or(body.len());
if end == 0 {
return Err(format!("'{rest}' starts like a number and is not one"));
}
let text: String = body[..end].chars().filter(|ch| *ch != '_').collect();
let value = u64::from_str_radix(&text, radix)
.map_err(|_| format!("'{text}' does not fit in sixty four bits"))?;
self.at += skip + end;
while self.text[self.at..].starts_with(['u', 'U', 'l', 'L']) {
self.at += 1;
}
Ok(Sum::just(value as i64))
}
fn character(&mut self) -> Result<Sum, String> {
self.at += 1;
let rest = &self.text[self.at..];
let mut chars = rest.chars();
let Some(first) = chars.next() else {
return Err("a quote with no character after it".to_owned());
};
let (value, used) = if first == '\\' {
let (value, used) = escape(&rest[1..])?;
(value, used + 1)
} else {
(first as u8, first.len_utf8())
};
self.at += used;
if self.text[self.at..].starts_with('\'') {
self.at += 1;
}
Ok(Sum::just(i64::from(value)))
}
fn arithmetic(&self, left: Sum, right: Sum, op: &str) -> Result<Sum, String> {
let (Some(a), Some(b)) = (left.flat(), right.flat()) else {
return Err(format!("'{op}' of something that names a symbol"));
};
Ok(Sum::just(self.arithmetic_number(a, b, op)?))
}
fn arithmetic_number(&self, a: i64, b: i64, op: &str) -> Result<i64, String> {
Ok(match op {
"|" => a | b,
"^" => a ^ b,
"&" => a & b,
"<<" => a.wrapping_shl(shift(b)?),
">>" => a.wrapping_shr(shift(b)?),
"*" => a.wrapping_mul(b),
"/" if b == 0 => return Err("a division by zero".to_owned()),
"%" if b == 0 => return Err("a remainder of a division by zero".to_owned()),
"/" => a.wrapping_div(b),
"%" => a.wrapping_rem(b),
_ => return Err(format!("'{op}' is not an operator this compiler knows")),
})
}
fn word(&mut self) -> String {
let body = &self.text[self.at..];
let end = body.find(|ch: char| !carries_on(ch as u8)).unwrap_or(body.len());
let word = body[..end].to_owned();
self.at += end;
word
}
fn one_of(&mut self, ops: &[&'static str]) -> Option<&'static str> {
for op in ops {
if self.text[self.at..].starts_with(op) {
self.at += op.len();
return Some(op);
}
}
None
}
fn eat(&mut self, what: &str) -> bool {
if self.text[self.at..].starts_with(what) {
self.at += what.len();
return true;
}
false
}
fn space(&mut self) {
while self.text[self.at..].starts_with([' ', '\t']) {
self.at += 1;
}
}
}
impl Reader {
fn string(&self, text: &str) -> Result<Vec<u8>, Trouble> {
let bad = |why: &str| Trouble { line: self.line, why: why.to_owned() };
let body = text
.strip_prefix('"')
.and_then(|rest| rest.strip_suffix('"'))
.ok_or_else(|| bad("a string directive whose operand is not in quotes"))?;
let mut out = Vec::with_capacity(body.len());
let mut at = 0;
while at < body.len() {
let rest = &body[at..];
let first = rest.as_bytes()[0];
if first == b'\\' {
let (value, used) =
escape(&rest[1..]).map_err(|why| Trouble { line: self.line, why })?;
out.push(value);
at += used + 1;
continue;
}
let ch = rest.chars().next().unwrap_or('\0');
let mut buffer = [0u8; 4];
out.extend_from_slice(ch.encode_utf8(&mut buffer).as_bytes());
at += ch.len_utf8();
}
Ok(out)
}
}
fn shift(by: i64) -> Result<u32, String> {
u32::try_from(by).map_err(|_| "a shift by a negative amount".to_owned())
}
fn escape(rest: &str) -> Result<(u8, usize), String> {
let bytes = rest.as_bytes();
let Some(&first) = bytes.first() else {
return Err("a backslash with nothing after it".to_owned());
};
let simple = match first {
b'n' => Some(b'\n'),
b't' => Some(b'\t'),
b'r' => Some(b'\r'),
b'f' => Some(0x0c),
b'b' => Some(0x08),
b'v' => Some(0x0b),
b'a' => Some(0x07),
b'e' => Some(0x1b),
b'\\' => Some(b'\\'),
b'"' => Some(b'"'),
b'\'' => Some(b'\''),
_ => None,
};
if let Some(value) = simple {
return Ok((value, 1));
}
if first == b'x' || first == b'X' {
let end = bytes[1..]
.iter()
.position(|byte| !byte.is_ascii_hexdigit())
.map_or(bytes.len(), |at| at + 1);
if end == 1 {
return Err("a hex escape with no digits in it".to_owned());
}
let text = &rest[1..end];
let text = &text[text.len().saturating_sub(2)..];
let value =
u8::from_str_radix(text, 16).map_err(|_| "a hex escape that is not one".to_owned())?;
return Ok((value, end));
}
if (b'0'..=b'7').contains(&first) {
let end = bytes.iter().take(3).take_while(|byte| (b'0'..=b'7').contains(byte)).count();
let value = u32::from_str_radix(&rest[..end], 8)
.map_err(|_| "an octal escape that is not one".to_owned())?;
return Ok(((value & 0xff) as u8, end));
}
Err(format!("'\\{}' is not an escape this compiler knows", first as char))
}
fn labelled(text: &str) -> Option<String> {
let bytes = text.as_bytes();
if bytes.is_empty() || !(starts(bytes[0]) || bytes[0].is_ascii_digit()) {
return None;
}
let end = text.find(|ch: char| !carries_on(ch as u8))?;
if bytes.get(end) != Some(&b':') || bytes.get(end + 1) == Some(&b':') {
return None;
}
Some(text[..end].to_owned())
}
fn assigned(text: &str) -> Option<(&str, &str)> {
let bytes = text.as_bytes();
if bytes.is_empty() || !starts(bytes[0]) {
return None;
}
let end = text.find(|ch: char| !carries_on(ch as u8)).unwrap_or(text.len());
let rest = text[end..].trim_start().strip_prefix('=')?;
if rest.starts_with('=') {
return None;
}
Some((&text[..end], rest.trim()))
}
fn starts(byte: u8) -> bool {
byte.is_ascii_alphabetic() || matches!(byte, b'_' | b'.' | b'$')
}
fn carries_on(byte: u8) -> bool {
starts(byte) || byte.is_ascii_digit()
}
fn counted(number: &str, nth: usize) -> String {
format!("{number}\u{1}{nth}")
}
fn unquoted(text: &str) -> String {
text.strip_prefix('"').and_then(|rest| rest.strip_suffix('"')).unwrap_or(text).to_owned()
}
pub(crate) fn split(text: &str, on: char) -> Vec<String> {
let mut out = Vec::new();
let mut piece = String::new();
let mut depth = 0i32;
let mut quote = None;
let mut chars = text.chars();
while let Some(ch) = chars.next() {
if let Some(mark) = quote {
piece.push(ch);
if ch == '\\' {
if let Some(next) = chars.next() {
piece.push(next);
}
continue;
}
if ch == mark {
quote = None;
}
continue;
}
match ch {
'"' => {
quote = Some(ch);
piece.push(ch);
}
'(' => {
depth += 1;
piece.push(ch);
}
')' => {
depth -= 1;
piece.push(ch);
}
_ if ch == on && depth == 0 => {
out.push(std::mem::take(&mut piece));
}
_ => piece.push(ch),
}
}
if !piece.trim().is_empty() || !out.is_empty() {
out.push(piece);
}
out.into_iter().map(|piece| piece.trim().to_owned()).collect()
}
fn repeated<'a>(word: &str, rest: &'a str) -> Option<(String, &'a str)> {
let unequal = match word {
"rep" | "repe" | "repz" => false,
"repne" | "repnz" => true,
_ => return None,
};
let (next, after) = match rest.find(char::is_whitespace) {
Some(cut) => (&rest[..cut], rest[cut..].trim()),
None => (rest, ""),
};
let string = next.len() == 5 && next.ends_with(['b', 'w', 'l', 'q']);
let which = if string { &next[..4] } else { "" };
let prefix = match (unequal, which) {
(false, "movs" | "stos") => "rep",
(false, "scas" | "cmps") => "repe",
(true, "scas" | "cmps") => "repne",
_ => return None,
};
Some((format!("{prefix} {next}"), after))
}
#[cfg(test)]
mod tests {
use super::*;
use rucc_object::Reference;
fn assembled(text: &str) -> Assembled {
match read(text) {
Ok(assembled) => assembled,
Err(trouble) => panic!("line {}: {}", trouble.line, trouble.why),
}
}
fn bytes(assembled: &Assembled, name: &str) -> Vec<u8> {
let part = assembled
.parts
.iter()
.find(|part| part.name == name)
.unwrap_or_else(|| panic!("there is no section called '{name}'"));
part.bytes.clone()
}
fn name<'a>(assembled: &'a Assembled, want: &str) -> &'a Name {
assembled
.names
.iter()
.find(|name| name.name == want)
.unwrap_or_else(|| panic!("there is no name called '{want}'"))
}
fn refused(text: &str) -> Trouble {
read(text).err().unwrap_or_else(|| panic!("this was read and should not have been"))
}
#[test]
fn a_repeat_prefix_is_read_with_the_string_instruction_behind_it() {
let assembled =
assembled("\t.text\n\trep movsl\n\trepnz scasb\n\trepz cmpsb\n\trep stosq\n");
assert_eq!(
bytes(&assembled, ".text"),
[0xF3, 0xA5, 0xF2, 0xAE, 0xF3, 0xA6, 0xF3, 0x48, 0xAB]
);
}
#[test]
fn a_number_is_a_label_a_file_may_write_as_many_times_as_it_likes() {
let out =
assembled("\t.text\nfoo:\n1:\tnop\n\tjmp 1b\n1:\tnop\n\tjmp 1f\n\tnop\n1:\tret\n");
let text = bytes(&out, ".text");
assert_eq!(
text,
vec![0x90, 0xe9, 0xfa, 0xff, 0xff, 0xff, 0x90, 0xe9, 0x01, 0, 0, 0, 0x90, 0xc3]
);
assert!(out.parts[0].relocs.is_empty(), "{:?}", out.parts[0].relocs);
let written: Vec<&str> = out.names.iter().map(|name| name.name.as_str()).collect();
assert_eq!(written, vec!["foo"]);
}
#[test]
fn a_numbered_label_with_nothing_on_the_side_it_names_is_refused() {
let back = refused("\t.text\n\tjmp 1b\n1:\tret\n");
assert!(back.why.contains("none above it"), "{}", back.why);
let forward = refused("\t.text\n1:\tnop\n\tjmp 1f\n\tret\n");
assert!(forward.why.contains("none below it"), "{}", forward.why);
}
#[test]
fn a_prefix_is_a_statement_of_its_own_and_the_byte_goes_in_front() {
let out = assembled("\t.text\n\trep;bsf %rdx, %rcx\n");
assert_eq!(bytes(&out, ".text"), vec![0xf3, 0x48, 0x0f, 0xbc, 0xca]);
let split = assembled("\t.text\n\trep\n\tmovsq\n");
assert_eq!(bytes(&split, ".text"), vec![0xf3, 0x48, 0xa5]);
let lock = assembled("\t.text\n\tlock;incl (%rdi)\n");
assert_eq!(bytes(&lock, ".text"), vec![0xf0, 0xff, 0x07]);
}
#[test]
fn a_reach_through_the_table_is_a_relocation_even_when_this_file_defines_the_name() {
let out = assembled("\t.text\n\tmovq table@GOTPCREL(%rip), %rdx\ntable:\n\t.quad 0\n");
let relocs = &out.parts[0].relocs;
assert_eq!(relocs.len(), 1);
assert_eq!(relocs[0].symbol, "table");
assert_eq!(relocs[0].kind, Reference::Got);
assert_eq!(relocs[0].addend, -4);
let out = assembled("\t.text\n\tmovq counter@GOTTPOFF(%rip), %rax\n");
assert_eq!(out.parts[0].relocs[0].kind, Reference::Thread);
}
#[test]
fn a_number_beside_a_name_in_a_displacement_is_part_of_what_the_linker_is_asked_for() {
let out = assembled("\t.text\n\tleaq -512+table(%rip), %r8\n\t.globl table\n");
let relocs = &out.parts[0].relocs;
assert_eq!(relocs.len(), 1);
assert_eq!(relocs[0].symbol, "table");
assert_eq!(relocs[0].addend, -516);
let named: Vec<&str> = out.names.iter().map(|name| name.name.as_str()).collect();
assert_eq!(named, ["table"]);
}
#[test]
fn a_name_taken_away_from_something_in_a_displacement_is_refused() {
refused("\t.text\n\tleaq 512-table(%rip), %r8\n");
}
#[test]
fn the_probe_gmp_writes() {
let out = assembled("\t.data\n\t.globl foo\n\t.long 0\nfoo:\n\t.byte 0\n");
assert_eq!(bytes(&out, ".data"), vec![0, 0, 0, 0, 0]);
let foo = name(&out, "foo");
assert_eq!(foo.at, Held::In { part: 0, offset: 4 });
assert_eq!(foo.binding, Binding::Global);
}
#[test]
fn every_width_of_number_is_the_bytes_it_says_it_is() {
let out = assembled(
"\t.data\n\t.byte 1\n\t.short 2\n\t.long 3\n\t.quad 4\n\t.byte 0x7f, 0377, 'a', '\\n'\n",
);
let mut want = vec![1, 2, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0];
want.extend_from_slice(&[0x7f, 0xff, b'a', b'\n']);
assert_eq!(bytes(&out, ".data"), want);
}
#[test]
fn a_number_that_is_negative_is_written_as_the_width_asked_for() {
let out = assembled("\t.data\n\t.short -1\n\t.long -2\n");
assert_eq!(bytes(&out, ".data"), vec![0xff, 0xff, 0xfe, 0xff, 0xff, 0xff]);
}
#[test]
fn the_three_kinds_of_string_differ_only_in_the_zero_on_the_end() {
let out = assembled("\t.data\n\t.ascii \"ab\"\n\t.asciz \"cd\"\n\t.string \"e\\tf\"\n");
assert_eq!(bytes(&out, ".data"), b"abcd\0e\tf\0".to_vec());
}
#[test]
fn space_and_fill_put_that_many_bytes_there() {
let out = assembled("\t.data\n\t.byte 1\n\t.zero 3\n\t.space 2, 0x41\n\t.fill 2, 1, 7\n");
assert_eq!(bytes(&out, ".data"), vec![1, 0, 0, 0, 0x41, 0x41, 7, 7]);
}
#[test]
fn aligning_moves_on_to_the_boundary_and_no_further() {
let out = assembled("\t.data\n\t.byte 1\n\t.align 8\n\t.byte 2\n\t.p2align 4\n\t.byte 3\n");
let data = bytes(&out, ".data");
assert_eq!(data.len(), 17);
assert_eq!(data[0], 1);
assert_eq!(data[8], 2);
assert_eq!(data[16], 3);
assert_eq!(out.parts[0].align, 16, "the section has to start where the widest ask does");
}
#[test]
fn a_section_that_holds_no_bytes_counts_them_rather_than_carrying_them() {
let out = assembled("\t.bss\n\t.globl room\nroom:\n\t.zero 4096\n");
let part = &out.parts[0];
assert_eq!(part.name, ".bss");
assert_eq!(part.size, 4096);
assert!(part.bytes.is_empty(), "the zeroes were carried after all");
assert!(!part.shape.bits);
}
#[test]
fn what_a_section_directive_said_about_a_section_is_what_it_is() {
let out = assembled("\t.section .init.text,\"ax\",@progbits\n\t.byte 0x90\n");
let part = out.parts.iter().find(|part| part.name == ".init.text").expect("the section");
assert!(part.shape.alloc && part.shape.exec && part.shape.bits);
assert!(!part.shape.write, "nothing said it was writable");
}
#[test]
fn the_same_section_named_twice_is_one_section_and_the_bytes_run_on() {
let out = assembled("\t.data\n\t.byte 1\n\t.text\n\t.byte 0x90\n\t.data\n\t.byte 2\n");
assert_eq!(bytes(&out, ".data"), vec![1, 2]);
assert_eq!(bytes(&out, ".text"), vec![0x90]);
}
#[test]
fn pushing_a_section_and_coming_back_leaves_the_first_one_where_it_was() {
let out = assembled(
"\t.data\n\t.byte 1\n\t.pushsection .rodata\n\t.byte 9\n\t.popsection\n\t.byte 2\n",
);
assert_eq!(bytes(&out, ".data"), vec![1, 2]);
assert_eq!(bytes(&out, ".rodata"), vec![9]);
}
#[test]
fn a_size_that_counts_from_here_back_to_a_label_is_a_number() {
let out = assembled(
"\t.text\n\t.globl f\n\t.type f, @function\nf:\n\t.byte 0,0,0,0,0\n\t.size f, .-f\n",
);
let f = name(&out, "f");
assert_eq!(f.size, 5);
assert_eq!(f.sort, Sort::Func);
}
#[test]
fn a_set_may_name_something_further_down_the_file() {
let out = assembled(
"\t.data\ntable:\n\t.long 1, 2, 3\ntable_end:\n\t.globl width\n\t.set width, \
table_end - table\n",
);
assert_eq!(name(&out, "width").at, Held::Absolute(12));
}
#[test]
fn a_set_that_names_another_set_is_worked_at_until_it_stops_moving() {
let out = assembled("\t.set a, b + 1\n\t.set b, c * 2\n\t.set c, 5\n");
assert_eq!(name(&out, "a").at, Held::Absolute(11));
assert_eq!(name(&out, "b").at, Held::Absolute(10));
}
#[test]
fn two_sets_that_name_each_other_are_refused_rather_than_looped_over() {
let why = refused("\t.set a, b\n\t.set b, a\n");
assert!(why.why.contains("neither has a value"), "{why}");
}
#[test]
fn a_pointer_to_something_else_is_a_relocation_for_the_whole_address() {
let out = assembled("\t.data\n\t.quad message\n");
let reloc = &out.parts[0].relocs[0];
assert_eq!(reloc.at, 0);
assert_eq!(reloc.symbol, "message");
assert_eq!(reloc.kind, Reference::Address { bytes: 8 });
assert_eq!(reloc.addend, 0);
assert_eq!(name(&out, "message").at, Held::Undefined);
}
#[test]
fn a_distance_from_here_to_something_else_is_a_relocation_relative_to_here() {
let out = assembled("\t.data\n\t.quad 0\n\t.long message - .\n");
let reloc = &out.parts[0].relocs[0];
assert_eq!(reloc.at, 8);
assert_eq!(reloc.symbol, "message");
assert_eq!(reloc.kind, Reference::Data);
assert_eq!(reloc.addend, 0);
}
#[test]
fn a_distance_counted_from_somewhere_that_is_not_here_carries_the_difference() {
let out = assembled("\t.data\nstart:\n\t.quad 0\n\t.long message - start\n");
let reloc = &out.parts[0].relocs[0];
assert_eq!(reloc.at, 8);
assert_eq!(reloc.kind, Reference::Data);
assert_eq!(reloc.addend, 8);
}
#[test]
fn a_number_added_to_a_name_rides_along_in_the_addend() {
let out = assembled("\t.data\n\t.quad message + 16\n");
assert_eq!(out.parts[0].relocs[0].addend, 16);
}
#[test]
fn comm_and_lcomm_ask_the_linker_for_room_rather_than_carrying_it() {
let out = assembled("\t.comm shared, 8, 8\n\t.lcomm mine, 32, 16\n");
assert_eq!(name(&out, "shared").at, Held::Common { size: 8, align: 8 });
assert_eq!(name(&out, "shared").binding, Binding::Global);
assert_eq!(name(&out, "mine").binding, Binding::Local);
assert!(matches!(name(&out, "mine").at, Held::In { .. }));
}
#[test]
fn what_a_file_says_about_who_can_see_a_name_is_kept() {
let out = assembled(
"\t.text\n\t.globl seen\n\t.weak maybe\n\t.hidden inside\n\t.globl \
inside\nseen:\nmaybe:\ninside:\n\t.byte 0\n",
);
assert_eq!(name(&out, "seen").binding, Binding::Global);
assert_eq!(name(&out, "maybe").binding, Binding::Weak);
assert_eq!(name(&out, "inside").visibility, Visibility::Hidden);
}
#[test]
fn the_name_of_the_file_is_a_symbol_of_its_own() {
let out = assembled("\t.file \"big.s\"\n\t.data\nbig:\n\t.byte 0\n");
assert_eq!(out.names[0].name, "big.s");
assert_eq!(out.names[0].sort, Sort::File);
assert_eq!(out.names[0].binding, Binding::Local);
assert!(out.names.iter().any(|name| name.name == "big"), "the label was lost");
}
#[test]
fn a_numbered_file_is_a_note_for_a_debugger_and_not_a_name() {
let out = assembled("\t.file 1 \"foo.c\"\n\t.data\n\t.byte 0\n");
assert!(out.names.is_empty(), "{:?}", out.names);
}
#[test]
fn an_instruction_this_has_no_bytes_for_is_refused_by_name_and_by_line() {
let why = refused("\t.text\nf:\n\tmovq %rdi, %rax\n\tpopcnt %rax, %rdx\n\tret\n");
assert_eq!(why.line, 4);
assert!(why.why.contains("popcnt"), "{why}");
}
#[test]
fn a_function_of_instructions_is_its_bytes_and_its_size() {
let out = assembled(
"\t.text\n\t.globl id\n\t.type id, @function\nid:\n\tmovq %rdi, %rax\n\tret\n\t.size \
id, .-id\n",
);
assert_eq!(bytes(&out, ".text"), vec![0x48, 0x89, 0xf8, 0xc3]);
assert_eq!(name(&out, "id").size, 4);
assert_eq!(name(&out, "id").at, Held::In { part: 0, offset: 0 });
}
#[test]
fn a_jump_to_a_label_in_this_section_is_a_number_and_not_a_relocation() {
let out = assembled("\t.text\n\tjmp over\nover:\n\tret\n");
assert_eq!(bytes(&out, ".text"), vec![0xe9, 0, 0, 0, 0, 0xc3]);
assert!(out.parts[0].relocs.is_empty(), "{:?}", out.parts[0].relocs);
}
#[test]
fn a_jump_backwards_is_the_negative_distance_to_it() {
let out = assembled("\t.text\nagain:\n\tjmp again\n");
assert_eq!(bytes(&out, ".text"), vec![0xe9, 0xfb, 0xff, 0xff, 0xff]);
}
#[test]
fn a_call_to_a_name_this_file_does_not_define_may_go_through_a_stub() {
let out = assembled("\t.text\n\tcall puts\n");
let reloc = &out.parts[0].relocs[0];
assert_eq!(reloc.at, 1);
assert_eq!(reloc.symbol, "puts");
assert_eq!(reloc.kind, Reference::Call);
assert_eq!(reloc.addend, -4);
}
#[test]
fn a_datum_reached_from_the_instruction_pointer_is_a_relocation_that_may_not() {
let out = assembled("\t.text\n\tmovq message(%rip), %rax\n");
let reloc = &out.parts[0].relocs[0];
assert_eq!(reloc.symbol, "message");
assert_eq!(reloc.kind, Reference::Data);
assert_eq!(reloc.at, 3);
assert_eq!(reloc.addend, -4);
}
#[test]
fn a_branch_with_one_byte_of_reach_is_filled_in_at_one_byte() {
let out = assembled("\t.text\nagain:\n\tdec %rcx\n\tjrcxz again\n\tret\n");
assert_eq!(bytes(&out, ".text"), vec![0x48, 0xff, 0xc9, 0xe3, 0xfb, 0xc3]);
}
#[test]
fn a_branch_to_somewhere_the_bytes_it_has_cannot_reach_is_refused() {
let why = refused("\t.text\n\tjrcxz away\n\t.zero 200\naway:\n\tret\n");
assert_eq!(why.line, 2);
assert!(why.why.contains("does not reach"), "{why}");
}
#[test]
fn a_number_too_big_for_the_bytes_it_is_written_into_is_refused() {
let out = assembled("\t.data\nhere:\n\t.zero 200\nthere:\n\t.byte there - here\n");
assert_eq!(bytes(&out, ".data")[200], 200);
let why = refused("\t.data\nhere:\n\t.zero 300\nthere:\n\t.byte there - here\n");
assert!(why.why.contains("does not reach"), "{why}");
}
#[test]
fn an_instruction_in_a_section_that_holds_no_bytes_is_refused() {
let why = refused("\t.bss\n\tret\n");
assert!(why.why.contains("holds no bytes"), "{why}");
}
#[test]
fn a_directive_this_does_not_know_is_refused_by_name_and_by_line() {
let why = refused("\t.text\n\t.byte 0\n\t.reloc 0, R_X86_64_NONE, f\n");
assert_eq!(why.line, 3);
assert!(why.why.contains(".reloc"), "{why}");
}
#[test]
fn the_comments_the_three_ways_of_writing_one_make_are_not_read() {
let out = assembled(
"# 1 \"foo.S\"\n\t.data\n\t.byte 1 # one\n\t.byte 2 // two\n\t/* a\n\tcomment */\t.byte \
3\n",
);
assert_eq!(bytes(&out, ".data"), vec![1, 2, 3]);
}
#[test]
fn a_comment_left_open_at_the_end_of_the_file_is_said_rather_than_ignored() {
let why = refused("\t.data\n\t/* and then nothing\n");
assert!(why.why.contains("never closed"), "{why}");
}
#[test]
fn a_string_with_a_comment_character_in_it_is_a_string() {
let out = assembled("\t.data\n\t.ascii \"a#b/*c\"\n");
assert_eq!(bytes(&out, ".data"), b"a#b/*c".to_vec());
}
#[test]
fn several_statements_on_one_line_are_several_statements() {
let out = assembled("\t.data; .byte 1; .byte 2\n");
assert_eq!(bytes(&out, ".data"), vec![1, 2]);
}
#[test]
fn a_section_nothing_was_ever_put_in_is_dropped() {
let out = assembled("\t.data\n\t.byte 1\n");
assert_eq!(out.parts.len(), 1);
assert_eq!(out.parts[0].name, ".data");
}
#[test]
fn a_section_with_nothing_in_it_but_a_name_is_kept() {
let out = assembled("\t.text\n\t.globl marker\nmarker:\n");
assert_eq!(out.parts.len(), 1);
assert_eq!(name(&out, "marker").at, Held::In { part: 0, offset: 0 });
}
#[test]
fn an_error_directive_is_the_file_saying_it_refuses_itself() {
let why = refused("\t.error \"this is not the machine for it\"\n");
assert!(why.why.contains("not the machine for it"), "{why}");
}
#[test]
fn a_jump_counted_from_itself_is_the_short_one_gas_writes() {
let out = assembled("\tjmp .+6\n\t.int 123\n\tmov .-4(%rip), %eax\n");
assert_eq!(
bytes(&out, ".text"),
vec![0xeb, 0x04, 123, 0, 0, 0, 0x8b, 0x05, 0xf6, 0xff, 0xff, 0xff]
);
}
#[test]
fn a_numbered_label_in_an_expression_is_a_place() {
let out =
assembled("2:\n\tjmp .+6\n1:\n\t.pushsection .data\n\t.long 1b - 2b\n\t.popsection\n");
assert_eq!(bytes(&out, ".data"), vec![2, 0, 0, 0]);
let out = assembled("\t.data\n\t.byte 0b101\n");
assert_eq!(bytes(&out, ".data"), vec![5]);
}
#[test]
fn a_number_an_instruction_carries_may_be_an_expression_over_labels() {
let out = assembled("3:\tmov $4f-3b, %eax\n4:\n");
assert_eq!(bytes(&out, ".text"), vec![0xb8, 5, 0, 0, 0]);
}
#[test]
fn a_number_an_instruction_carries_may_not_name_something_elsewhere() {
let why = refused("\tmov $elsewhere, %eax\n");
assert!(why.why.contains("relocation"), "{why}");
}
#[test]
fn a_name_set_twice_means_what_it_was_where_it_is_used() {
let out = assembled(
"\t.data\n\t.byte early\n\tearly = 3\n\tx = 1\n\t.byte x\n\tx = x + 1\n\t.byte x\n",
);
assert_eq!(bytes(&out, ".data"), vec![3, 1, 2]);
}
#[test]
fn a_place_set_twice_and_reached_from_another_section_is_relocated_against() {
let out = assembled(
"\t.data\n\tx = .\n\t.int 1\n\tx = .\n\t.int 2\n\t.text\n\tmov x(%rip), %eax\n",
);
let reloc = &out.parts.iter().find(|part| part.name == ".text").unwrap().relocs[0];
let target = name(&out, &reloc.symbol);
let data = out.parts.iter().position(|part| part.name == ".data").unwrap();
assert_eq!(target.at, Held::In { part: data, offset: 4 });
}
}