use std::fmt;
use crate::aarch64::encode::{
Addr, Arrangement, Cond, Extend, Mode, Offset, Operator, Scalar, Shift, Value, Width,
};
#[derive(Debug, Clone, PartialEq)]
pub struct Line {
pub mnemonic: String,
pub values: Vec<Value>,
pub symbol: Option<String>,
pub addend: i64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Error {
pub text: String,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "cannot read `{}` as aarch64 assembly", self.text)
}
}
impl std::error::Error for Error {}
fn error(text: &str) -> Error {
Error { text: text.to_owned() }
}
pub fn read(text: &str) -> Result<Line, Error> {
let text = text.trim();
let (mnemonic, rest) = match text.find(char::is_whitespace) {
Some(at) => (&text[..at], text[at..].trim()),
None => (text, ""),
};
if mnemonic.is_empty() {
return Err(error(text));
}
let mut line = Line {
mnemonic: mnemonic.to_ascii_lowercase(),
values: Vec::new(),
symbol: None,
addend: 0,
};
let mut named = (None, 0);
let pieces = split(rest);
let mut at = 0;
while at < pieces.len() {
let piece = pieces[at];
if piece.starts_with('[') {
let mut addr = address(piece, &mut named)?;
if addr.mode == Mode::Offset && piece.ends_with(']') && at + 1 < pieces.len() {
if let (Offset::Imm(0), Some(imm)) =
(addr.offset, pieces[at + 1].strip_prefix('#').and_then(number))
{
addr.offset = Offset::Imm(imm);
addr.mode = Mode::Post;
at += 1;
}
}
line.values.push(Value::Mem(addr));
} else {
line.values.push(operand(piece, &mut named)?);
}
at += 1;
}
(line.symbol, line.addend) = named;
Ok(line)
}
type Named = (Option<String>, i64);
fn split(text: &str) -> Vec<&str> {
let mut pieces = Vec::new();
let (mut depth, mut start) = (0, 0);
for (at, c) in text.char_indices() {
match c {
'[' => depth += 1,
']' => depth -= 1,
',' if depth == 0 => {
pieces.push(text[start..at].trim());
start = at + 1;
}
_ => {}
}
}
if !text[start..].trim().is_empty() {
pieces.push(text[start..].trim());
}
pieces
}
fn operand(piece: &str, symbol: &mut Named) -> Result<Value, Error> {
let lower = piece.to_ascii_lowercase();
if let Some(value) = register(&lower) {
return Ok(value);
}
if let Some(imm) = lower.strip_prefix('#') {
if let Some(value) = number(imm) {
return Ok(Value::Imm(value));
}
if let Ok(value) = imm.parse::<f64>() {
return Ok(Value::Float(value));
}
if imm.starts_with(':') {
return reference(piece.trim_start_matches('#'), symbol).map(Value::Symbol);
}
return Err(error(piece));
}
if let Some(value) = modifier(&lower)? {
return Ok(value);
}
if let Some(cond) = Cond::named(&lower) {
return Ok(Value::Cond(cond));
}
if let Some(option) = barrier(&lower) {
return Ok(Value::Barrier(option));
}
if let Some(field) = system(&lower) {
return Ok(Value::System(field));
}
reference(piece, symbol).map(Value::Symbol)
}
fn modifier(lower: &str) -> Result<Option<Value>, Error> {
let (name, amount) = match lower.split_once(char::is_whitespace) {
Some((name, amount)) => (name, Some(amount.trim())),
None => (lower, None),
};
let amount = match amount {
Some(amount) => {
let digits = amount.strip_prefix('#').unwrap_or(amount);
let value = number(digits).and_then(|n| u8::try_from(n).ok());
Some(value.ok_or_else(|| error(lower))?)
}
None => None,
};
let shift = match name {
"lsl" => Some(Shift::Lsl),
"lsr" => Some(Shift::Lsr),
"asr" => Some(Shift::Asr),
"ror" => Some(Shift::Ror),
_ => None,
};
if let Some(shift) = shift {
return amount.map(|amount| Some(Value::Shift(shift, amount))).ok_or_else(|| error(lower));
}
Ok(extension(name).map(|extend| Value::Extend(extend, amount)))
}
fn extension(name: &str) -> Option<Extend> {
Some(match name {
"uxtb" => Extend::Uxtb,
"uxth" => Extend::Uxth,
"uxtw" => Extend::Uxtw,
"uxtx" => Extend::Uxtx,
"sxtb" => Extend::Sxtb,
"sxth" => Extend::Sxth,
"sxtw" => Extend::Sxtw,
"sxtx" => Extend::Sxtx,
_ => return None,
})
}
fn register(name: &str) -> Option<Value> {
match name {
"sp" => return Some(Value::Sp(Width::X)),
"wsp" => return Some(Value::Sp(Width::W)),
"xzr" => return Some(Value::Gpr(Width::X, 31)),
"wzr" => return Some(Value::Gpr(Width::W, 31)),
"fp" => return Some(Value::Gpr(Width::X, 29)),
"lr" => return Some(Value::Gpr(Width::X, 30)),
_ => {}
}
if let Some((number, lanes)) = name.strip_prefix('v').and_then(|rest| rest.split_once('.')) {
let arrangement = match lanes {
"8b" => Arrangement::B8,
"16b" => Arrangement::B16,
"2d" => Arrangement::D2,
_ => return None,
};
return Some(Value::Vector(arrangement, numbered(number, 32)?));
}
let (first, number) = name.split_at(name.char_indices().nth(1)?.0);
Some(match first {
"x" => Value::Gpr(Width::X, numbered(number, 31)?),
"w" => Value::Gpr(Width::W, numbered(number, 31)?),
"b" => Value::Fp(Scalar::B, numbered(number, 32)?),
"h" => Value::Fp(Scalar::H, numbered(number, 32)?),
"s" => Value::Fp(Scalar::S, numbered(number, 32)?),
"d" => Value::Fp(Scalar::D, numbered(number, 32)?),
"q" => Value::Fp(Scalar::Q, numbered(number, 32)?),
_ => return None,
})
}
fn numbered(text: &str, below: u8) -> Option<u8> {
if text.len() > 1 && text.starts_with('0') {
return None;
}
text.parse::<u8>().ok().filter(|&number| number < below)
}
fn number(text: &str) -> Option<i64> {
let (negative, digits) = match text.strip_prefix('-') {
Some(digits) => (true, digits),
None => (false, text),
};
let magnitude = match digits.strip_prefix("0x").or_else(|| digits.strip_prefix("0X")) {
Some(hex) => u64::from_str_radix(hex, 16).ok()?,
None => digits.parse::<u64>().ok()?,
};
let value = magnitude as i64;
Some(if negative { value.wrapping_neg() } else { value })
}
fn reference(text: &str, symbol: &mut Named) -> Result<Operator, Error> {
let (operator, name) = match text.strip_prefix(':') {
Some(rest) => {
let (operator, name) = rest.split_once(':').ok_or_else(|| error(text))?;
let operator = match operator.to_ascii_lowercase().as_str() {
"lo12" => Operator::Lo12,
"got" => Operator::Got,
"got_lo12" => Operator::GotLo12,
"gottprel" => Operator::GotTprel,
"gottprel_lo12" => Operator::GotTprelLo12,
"tprel_hi12" => Operator::TprelHi12,
"tprel_lo12_nc" => Operator::TprelLo12Nc,
_ => return Err(error(text)),
};
(operator, name)
}
None => (Operator::Plain, text),
};
let (name, addend) = match name.char_indices().skip(1).find(|&(_, c)| c == '+' || c == '-') {
Some((at, sign)) => {
let digits = name[at + 1..].trim();
let magnitude = number(digits).filter(|_| !digits.starts_with('-'));
let magnitude = magnitude.ok_or_else(|| error(text))?;
(name[..at].trim(), if sign == '-' { magnitude.wrapping_neg() } else { magnitude })
}
None => (name, 0),
};
let symbolic =
name.chars().next().is_some_and(|c| c.is_ascii_alphabetic() || "_.$".contains(c))
&& name.chars().all(|c| c.is_ascii_alphanumeric() || "_.$".contains(c));
let numbered = name.len() > 1
&& name.ends_with(['b', 'f'])
&& name[..name.len() - 1].bytes().all(|b| b.is_ascii_digit());
if !symbolic && !numbered {
return Err(error(text));
}
*symbol = (Some(name.to_owned()), addend);
Ok(operator)
}
pub(super) static BARRIERS: [(&str, u8); 12] = [
("oshld", 1),
("oshst", 2),
("osh", 3),
("nshld", 5),
("nshst", 6),
("nsh", 7),
("ishld", 9),
("ishst", 10),
("ish", 11),
("ld", 13),
("st", 14),
("sy", 15),
];
fn barrier(name: &str) -> Option<u8> {
BARRIERS.iter().find(|(known, _)| *known == name).map(|&(_, option)| option)
}
pub(super) static SYSTEM: [(&str, [u16; 5]); 9] = [
("nzcv", [3, 3, 4, 2, 0]),
("fpcr", [3, 3, 4, 4, 0]),
("fpsr", [3, 3, 4, 4, 1]),
("tpidr_el0", [3, 3, 13, 0, 2]),
("tpidrro_el0", [3, 3, 13, 0, 3]),
("cntfrq_el0", [3, 3, 14, 0, 0]),
("cntvct_el0", [3, 3, 14, 0, 2]),
("dczid_el0", [3, 3, 0, 0, 7]),
("ctr_el0", [3, 3, 0, 0, 1]),
];
pub(super) const fn system_field([op0, op1, crn, crm, op2]: [u16; 5]) -> u16 {
(op0 - 2) << 14 | op1 << 11 | crn << 7 | crm << 3 | op2
}
fn system(name: &str) -> Option<u16> {
if let Some(&(_, fields)) = SYSTEM.iter().find(|(known, _)| *known == name) {
return Some(system_field(fields));
}
let mut parts = name.strip_prefix('s')?.split('_');
let mut next = |prefix: &str, below: u16| {
let part = parts.next()?;
part.strip_prefix(prefix)?.parse::<u16>().ok().filter(|&n| n < below)
};
let fields = [next("", 4)?, next("", 8)?, next("c", 16)?, next("c", 16)?, next("", 8)?];
if parts.next().is_some() || fields[0] < 2 {
return None;
}
Some(system_field(fields))
}
fn address(piece: &str, symbol: &mut Named) -> Result<Addr, Error> {
let (inner, mode) = if let Some(inner) = piece.strip_suffix("]!") {
(inner, Mode::Pre)
} else {
(piece.strip_suffix(']').ok_or_else(|| error(piece))?, Mode::Offset)
};
let inner = inner.strip_prefix('[').ok_or_else(|| error(piece))?;
let parts: Vec<&str> = inner.split(',').map(str::trim).collect();
let base = match register(&parts[0].to_ascii_lowercase()) {
Some(Value::Sp(Width::X)) => 31,
Some(Value::Gpr(Width::X, number)) if number < 31 => number,
_ => return Err(error(piece)),
};
let offset = match &parts[1..] {
[] => Offset::Imm(0),
[imm] if imm.starts_with("#:") || imm.starts_with(':') => {
let text = imm.strip_prefix('#').unwrap_or(imm);
Offset::Symbol(reference(text, symbol)?)
}
[imm] if imm.starts_with('#') => {
Offset::Imm(imm.strip_prefix('#').and_then(number).ok_or_else(|| error(piece))?)
}
[index, rest @ ..] => {
let (width, reg) = match register(&index.to_ascii_lowercase()) {
Some(Value::Gpr(width, number)) if number < 31 => (width, number),
_ => return Err(error(piece)),
};
let (extend, amount) = match rest {
[] => (Extend::Uxtx, None),
[modifier_text] => match modifier(&modifier_text.to_ascii_lowercase())? {
Some(Value::Shift(Shift::Lsl, amount)) => (Extend::Uxtx, Some(amount)),
Some(Value::Extend(extend, amount)) => (extend, amount),
_ => return Err(error(piece)),
},
_ => return Err(error(piece)),
};
let reads_w = matches!(extend, Extend::Uxtw | Extend::Sxtw);
if reads_w != (width == Width::W) {
return Err(error(piece));
}
Offset::Reg { reg, extend, amount }
}
};
if mode == Mode::Pre && !matches!(offset, Offset::Imm(_)) {
return Err(error(piece));
}
Ok(Addr { base, offset, mode })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_address_is_one_operand_however_it_is_written() {
let line = read("ldr x0, [x1], #16").unwrap();
assert_eq!(
line.values,
[
Value::Gpr(Width::X, 0),
Value::Mem(Addr { base: 1, offset: Offset::Imm(16), mode: Mode::Post })
]
);
let line = read("str x30, [sp, #-16]!").unwrap();
assert_eq!(
line.values[1],
Value::Mem(Addr { base: 31, offset: Offset::Imm(-16), mode: Mode::Pre })
);
}
#[test]
fn a_symbol_keeps_its_name() {
let line = read("ldr x0, [x0, :got_lo12:environ]").unwrap();
assert_eq!(line.symbol.as_deref(), Some("environ"));
let line = read("bl printf").unwrap();
assert_eq!(line.values, [Value::Symbol(Operator::Plain)]);
assert_eq!(line.symbol.as_deref(), Some("printf"));
let line = read("add x0, x0, :lo12:table+24").unwrap();
assert_eq!((line.symbol.as_deref(), line.addend), (Some("table"), 24));
let line = read("adrp x1, names-8").unwrap();
assert_eq!((line.symbol.as_deref(), line.addend), (Some("names"), -8));
let line = read("cbnz w3, 1b").unwrap();
assert_eq!((line.symbol.as_deref(), line.addend), (Some("1b"), 0));
assert!(read("b 1x").is_err());
assert!(read("b table+").is_err());
}
#[test]
fn what_is_not_a_register_is_refused() {
assert!(read("ldr x0, [xzr]").is_err());
assert!(read("ldr x0, [x1, w2]").is_err());
assert!(read("add x0, x1, #zz").is_err());
assert_eq!(system("s3_3_c13_c0_2"), system("tpidr_el0"));
}
}