mod encode;
mod insts;
mod read;
mod text;
mod timing;
mod write;
pub use crate::aarch64::encode::{
Addr, Arrangement, Cond, Encoded, Error, Extend, Fixup, Mode, Offset, Operator, Scalar, Shift,
Value, Width, encode,
};
pub use crate::aarch64::insts::{ADDRESSES, Form, INSTS, TEMPLATE, address, form};
pub use crate::aarch64::read::{Error as ReadError, Line, read};
pub use crate::aarch64::text::{Arg, Missing, Operands, Written, fill, operand_width, written};
pub use crate::aarch64::timing::{LOAD, MODEL, TIMING};
pub use crate::aarch64::write::{Spelling, cond_name, write};
use crate::bits::BitInsts;
use crate::branch::{BranchInsts, Fusion, Move};
use crate::flags::{Compare, FlagInsts, Reader, Reads};
use crate::frame::{ClassMoves, FrameInsts, Pair, Probe};
use crate::machine::MachineInsts;
use crate::operand::OperandDesc;
use crate::regs::{CallRegs, ClassInfo, PhysReg, RegClass, RegFile};
use crate::short::ShortInsts;
pub const GPR: RegClass = RegClass::new(0);
pub const FPR: RegClass = RegClass::new(1);
#[must_use]
pub const fn x(number: u8) -> PhysReg {
assert!(number < 31, "x31 is not a general purpose register, it is sp or xzr");
PhysReg::new(number)
}
#[must_use]
pub const fn v(number: u8) -> PhysReg {
assert!(number < 32, "AArch64 has thirty two vector registers");
PhysReg::new(number)
}
pub const X8: PhysReg = x(8);
pub const X16: PhysReg = x(16);
pub const X17: PhysReg = x(17);
pub const X18: PhysReg = x(18);
pub const FP: PhysReg = x(29);
pub const LR: PhysReg = x(30);
pub const SP: PhysReg = PhysReg::new(31);
#[must_use]
pub fn named(name: &str) -> Option<(PhysReg, RegClass)> {
match name {
"fp" => return Some((FP, GPR)),
"lr" => return Some((LR, GPR)),
_ => {}
}
let (file, number) = name.split_at_checked(1)?;
if number.len() > 1 && number.starts_with('0') {
return None;
}
let number: u8 = number.parse().ok()?;
match file {
"x" | "w" if number < 31 => Some((x(number), GPR)),
"v" | "q" | "d" | "s" | "h" | "b" if number < 32 => Some((v(number), FPR)),
_ => None,
}
}
static GPR_NAMES: [&str; 32] = [
"x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", "x14",
"x15", "x16", "x17", "x18", "x19", "x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27",
"x28", "x29", "x30", "sp",
];
static FPR_NAMES: [&str; 32] = [
"v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9", "v10", "v11", "v12", "v13", "v14",
"v15", "v16", "v17", "v18", "v19", "v20", "v21", "v22", "v23", "v24", "v25", "v26", "v27",
"v28", "v29", "v30", "v31",
];
static CLASSES: [ClassInfo; 2] = [
ClassInfo { name: "gpr", bits: 64, regs: &GPR_NAMES, allocatable: true },
ClassInfo { name: "fpr", bits: 128, regs: &FPR_NAMES, allocatable: true },
];
pub static REGS: RegFile = RegFile::new(&CLASSES);
static GPR_DWARF: [u16; 32] = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31,
];
static FPR_DWARF: [u16; 32] = [
64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87,
88, 89, 90, 91, 92, 93, 94, 95,
];
static AARCH64_DWARF: [&[u16]; 2] = [&GPR_DWARF, &FPR_DWARF];
pub const DWARF_RETURN_ADDRESS: u16 = 30;
static AARCH64_MOVES: [ClassMoves; 2] = [
ClassMoves { mov: "mov_rr_64", load: "ldr_64", store: "str_64" },
ClassMoves { mov: "mov_rr_f128", load: "ldr_f128", store: "str_f128" },
];
pub static FRAME: FrameInsts = FrameInsts {
prefix: "a64.",
classes: &AARCH64_MOVES,
push: "push_64",
pop: "pop_64",
pair: Some(Pair { push: "push_pair_64", pop: "pop_pair_64" }),
add: "add_ri_64",
sub: "sub_ri_64",
grow: "sub_rr_64",
align: "align_sp_64",
imm: "mov_ri_64",
lea: "lea_64",
sum: "add_rr_64",
ret: "ret",
differ: "cmp_set_ne_64",
above: "cmp_set_hi_64",
call: "bl",
probe: Some(PROBE),
landing: None,
pad: Some("nop"),
step_bits: Some(12),
reaches: Some(frame_reaches),
};
fn frame_reaches(name: &str, disp: i32) -> bool {
let Some(insts) = written(name) else { return true };
let mem = Addr { base: 31, offset: Offset::Imm(i64::from(disp)), mode: Mode::Offset };
let with = Operands { regs: &[0; 8], reads: 0, imm: 0, mem: Some(mem) };
insts.iter().all(|inst| {
let Ok(values) =
inst.args.iter().map(|&arg| fill(arg, &with)).collect::<Result<Vec<_>, _>>()
else {
return true;
};
!matches!(encode(inst.mnemonic, &values), Err(Error::Immediate { .. }))
})
}
pub static PROBE: Probe = Probe { inst: "probe_64", interval: 4096 };
pub static BITS: BitInsts = BitInsts { prefix: "a64.", width: bit_width, copies_low };
#[must_use]
fn bit_width(name: &str, operand: u8) -> Option<u32> {
if operand == 1 && copies_low(name) {
match name.as_bytes().get(3) {
Some(b'b') => return Some(8),
Some(b'h') => return Some(16),
Some(b'w') => return Some(32),
_ => {}
}
}
operand_width(name, operand)
}
pub static MACHINE: MachineInsts = MachineInsts {
prefix: "a64.",
operands: machine_operands,
takes_imm: machine_takes_imm,
takes_mem: machine_takes_mem,
touches_mem: machine_touches_mem,
calls: machine_calls,
scales: &[1],
index_and_disp: false,
};
#[must_use]
fn machine_operands(name: &str) -> Option<&'static [OperandDesc]> {
form(name).map(Form::operands)
}
#[must_use]
fn machine_takes_imm(name: &str) -> bool {
form(name).is_some_and(Form::takes_imm)
}
#[must_use]
fn machine_takes_mem(name: &str) -> bool {
form(name).is_some_and(Form::takes_mem)
}
#[must_use]
fn machine_touches_mem(name: &str) -> bool {
form(name).is_some_and(Form::touches_mem)
}
#[must_use]
fn machine_calls(name: &str) -> bool {
form(name) == Some(Form::Call)
}
#[must_use]
fn copies_low(name: &str) -> bool {
form(name) == Some(Form::Convert)
}
pub static BRANCH: BranchInsts = BranchInsts {
prefix: "a64.",
cond: "br_cond_32",
test: "test_32",
if_true: "b_ne",
if_false: "b_eq",
jump: "b",
indirect: "br",
conditional: &CONDITIONAL,
fused: &FUSED,
moves: &MOVES,
};
static CONDITIONAL: [&str; 14] = [
"b_eq", "b_ne", "b_lt", "b_le", "b_gt", "b_ge", "b_lo", "b_ls", "b_hi", "b_hs", "b_mi", "b_pl",
"b_vs", "b_vc",
];
static FUSED: [Fusion; 40] = [
Fusion { set: "cmp_set_eq_32", cmp: "cmp_rr_32", if_true: "b_eq", if_false: "b_ne" },
Fusion { set: "cmp_set_eq_64", cmp: "cmp_rr_64", if_true: "b_eq", if_false: "b_ne" },
Fusion { set: "cmp_set_ne_32", cmp: "cmp_rr_32", if_true: "b_ne", if_false: "b_eq" },
Fusion { set: "cmp_set_ne_64", cmp: "cmp_rr_64", if_true: "b_ne", if_false: "b_eq" },
Fusion { set: "cmp_set_lt_32", cmp: "cmp_rr_32", if_true: "b_lt", if_false: "b_ge" },
Fusion { set: "cmp_set_lt_64", cmp: "cmp_rr_64", if_true: "b_lt", if_false: "b_ge" },
Fusion { set: "cmp_set_le_32", cmp: "cmp_rr_32", if_true: "b_le", if_false: "b_gt" },
Fusion { set: "cmp_set_le_64", cmp: "cmp_rr_64", if_true: "b_le", if_false: "b_gt" },
Fusion { set: "cmp_set_gt_32", cmp: "cmp_rr_32", if_true: "b_gt", if_false: "b_le" },
Fusion { set: "cmp_set_gt_64", cmp: "cmp_rr_64", if_true: "b_gt", if_false: "b_le" },
Fusion { set: "cmp_set_ge_32", cmp: "cmp_rr_32", if_true: "b_ge", if_false: "b_lt" },
Fusion { set: "cmp_set_ge_64", cmp: "cmp_rr_64", if_true: "b_ge", if_false: "b_lt" },
Fusion { set: "cmp_set_lo_32", cmp: "cmp_rr_32", if_true: "b_lo", if_false: "b_hs" },
Fusion { set: "cmp_set_lo_64", cmp: "cmp_rr_64", if_true: "b_lo", if_false: "b_hs" },
Fusion { set: "cmp_set_ls_32", cmp: "cmp_rr_32", if_true: "b_ls", if_false: "b_hi" },
Fusion { set: "cmp_set_ls_64", cmp: "cmp_rr_64", if_true: "b_ls", if_false: "b_hi" },
Fusion { set: "cmp_set_hi_32", cmp: "cmp_rr_32", if_true: "b_hi", if_false: "b_ls" },
Fusion { set: "cmp_set_hi_64", cmp: "cmp_rr_64", if_true: "b_hi", if_false: "b_ls" },
Fusion { set: "cmp_set_hs_32", cmp: "cmp_rr_32", if_true: "b_hs", if_false: "b_lo" },
Fusion { set: "cmp_set_hs_64", cmp: "cmp_rr_64", if_true: "b_hs", if_false: "b_lo" },
Fusion { set: "cmp_set_eq_ri_32", cmp: "cmp_ri_32", if_true: "b_eq", if_false: "b_ne" },
Fusion { set: "cmp_set_eq_ri_64", cmp: "cmp_ri_64", if_true: "b_eq", if_false: "b_ne" },
Fusion { set: "cmp_set_ne_ri_32", cmp: "cmp_ri_32", if_true: "b_ne", if_false: "b_eq" },
Fusion { set: "cmp_set_ne_ri_64", cmp: "cmp_ri_64", if_true: "b_ne", if_false: "b_eq" },
Fusion { set: "cmp_set_lt_ri_32", cmp: "cmp_ri_32", if_true: "b_lt", if_false: "b_ge" },
Fusion { set: "cmp_set_lt_ri_64", cmp: "cmp_ri_64", if_true: "b_lt", if_false: "b_ge" },
Fusion { set: "cmp_set_le_ri_32", cmp: "cmp_ri_32", if_true: "b_le", if_false: "b_gt" },
Fusion { set: "cmp_set_le_ri_64", cmp: "cmp_ri_64", if_true: "b_le", if_false: "b_gt" },
Fusion { set: "cmp_set_gt_ri_32", cmp: "cmp_ri_32", if_true: "b_gt", if_false: "b_le" },
Fusion { set: "cmp_set_gt_ri_64", cmp: "cmp_ri_64", if_true: "b_gt", if_false: "b_le" },
Fusion { set: "cmp_set_ge_ri_32", cmp: "cmp_ri_32", if_true: "b_ge", if_false: "b_lt" },
Fusion { set: "cmp_set_ge_ri_64", cmp: "cmp_ri_64", if_true: "b_ge", if_false: "b_lt" },
Fusion { set: "cmp_set_lo_ri_32", cmp: "cmp_ri_32", if_true: "b_lo", if_false: "b_hs" },
Fusion { set: "cmp_set_lo_ri_64", cmp: "cmp_ri_64", if_true: "b_lo", if_false: "b_hs" },
Fusion { set: "cmp_set_ls_ri_32", cmp: "cmp_ri_32", if_true: "b_ls", if_false: "b_hi" },
Fusion { set: "cmp_set_ls_ri_64", cmp: "cmp_ri_64", if_true: "b_ls", if_false: "b_hi" },
Fusion { set: "cmp_set_hi_ri_32", cmp: "cmp_ri_32", if_true: "b_hi", if_false: "b_ls" },
Fusion { set: "cmp_set_hi_ri_64", cmp: "cmp_ri_64", if_true: "b_hi", if_false: "b_ls" },
Fusion { set: "cmp_set_hs_ri_32", cmp: "cmp_ri_32", if_true: "b_hs", if_false: "b_lo" },
Fusion { set: "cmp_set_hs_ri_64", cmp: "cmp_ri_64", if_true: "b_hs", if_false: "b_lo" },
];
static MOVES: [Move; 20] = [
Move { select: "sel_32", when: "b_eq", cmov: "csel_eq_32" },
Move { select: "sel_32", when: "b_ne", cmov: "csel_ne_32" },
Move { select: "sel_32", when: "b_lt", cmov: "csel_lt_32" },
Move { select: "sel_32", when: "b_le", cmov: "csel_le_32" },
Move { select: "sel_32", when: "b_gt", cmov: "csel_gt_32" },
Move { select: "sel_32", when: "b_ge", cmov: "csel_ge_32" },
Move { select: "sel_32", when: "b_lo", cmov: "csel_lo_32" },
Move { select: "sel_32", when: "b_ls", cmov: "csel_ls_32" },
Move { select: "sel_32", when: "b_hi", cmov: "csel_hi_32" },
Move { select: "sel_32", when: "b_hs", cmov: "csel_hs_32" },
Move { select: "sel_64", when: "b_eq", cmov: "csel_eq_64" },
Move { select: "sel_64", when: "b_ne", cmov: "csel_ne_64" },
Move { select: "sel_64", when: "b_lt", cmov: "csel_lt_64" },
Move { select: "sel_64", when: "b_le", cmov: "csel_le_64" },
Move { select: "sel_64", when: "b_gt", cmov: "csel_gt_64" },
Move { select: "sel_64", when: "b_ge", cmov: "csel_ge_64" },
Move { select: "sel_64", when: "b_lo", cmov: "csel_lo_64" },
Move { select: "sel_64", when: "b_ls", cmov: "csel_ls_64" },
Move { select: "sel_64", when: "b_hi", cmov: "csel_hi_64" },
Move { select: "sel_64", when: "b_hs", cmov: "csel_hs_64" },
];
pub static FLAGS: FlagInsts = FlagInsts {
prefix: "a64.",
width: operand_width,
writes: writes_flags,
compares: &COMPARES,
readers: &READERS,
compares_itself,
zeroing: &[],
};
pub static SHORT: ShortInsts = ShortInsts {
prefix: "a64.",
zeroing: &[],
narrowing: &[],
testing: &[],
stepping: &[],
copying: &[],
};
fn compares_itself(name: &str) -> bool {
matches!(form(name), Some(Form::CmpSet | Form::CmpSetI | Form::FCmpSet))
}
#[must_use]
fn writes_flags(name: &str) -> bool {
form(name).is_none_or(|shape| {
matches!(
shape,
Form::Cmp
| Form::CmpI
| Form::Test
| Form::CmpSet
| Form::CmpSetI
| Form::Select
| Form::FCmp
| Form::FCmpSet
| Form::Call
)
})
}
static COMPARES: [Compare; 44] = [
Compare { name: "cmp_set_eq_32", asks: "cmp_rr_32", kept: Some("cset_eq") },
Compare { name: "cmp_set_eq_64", asks: "cmp_rr_64", kept: Some("cset_eq") },
Compare { name: "cmp_set_ne_32", asks: "cmp_rr_32", kept: Some("cset_ne") },
Compare { name: "cmp_set_ne_64", asks: "cmp_rr_64", kept: Some("cset_ne") },
Compare { name: "cmp_set_lt_32", asks: "cmp_rr_32", kept: Some("cset_lt") },
Compare { name: "cmp_set_lt_64", asks: "cmp_rr_64", kept: Some("cset_lt") },
Compare { name: "cmp_set_le_32", asks: "cmp_rr_32", kept: Some("cset_le") },
Compare { name: "cmp_set_le_64", asks: "cmp_rr_64", kept: Some("cset_le") },
Compare { name: "cmp_set_gt_32", asks: "cmp_rr_32", kept: Some("cset_gt") },
Compare { name: "cmp_set_gt_64", asks: "cmp_rr_64", kept: Some("cset_gt") },
Compare { name: "cmp_set_ge_32", asks: "cmp_rr_32", kept: Some("cset_ge") },
Compare { name: "cmp_set_ge_64", asks: "cmp_rr_64", kept: Some("cset_ge") },
Compare { name: "cmp_set_lo_32", asks: "cmp_rr_32", kept: Some("cset_lo") },
Compare { name: "cmp_set_lo_64", asks: "cmp_rr_64", kept: Some("cset_lo") },
Compare { name: "cmp_set_ls_32", asks: "cmp_rr_32", kept: Some("cset_ls") },
Compare { name: "cmp_set_ls_64", asks: "cmp_rr_64", kept: Some("cset_ls") },
Compare { name: "cmp_set_hi_32", asks: "cmp_rr_32", kept: Some("cset_hi") },
Compare { name: "cmp_set_hi_64", asks: "cmp_rr_64", kept: Some("cset_hi") },
Compare { name: "cmp_set_hs_32", asks: "cmp_rr_32", kept: Some("cset_hs") },
Compare { name: "cmp_set_hs_64", asks: "cmp_rr_64", kept: Some("cset_hs") },
Compare { name: "cmp_set_eq_ri_32", asks: "cmp_ri_32", kept: Some("cset_eq") },
Compare { name: "cmp_set_eq_ri_64", asks: "cmp_ri_64", kept: Some("cset_eq") },
Compare { name: "cmp_set_ne_ri_32", asks: "cmp_ri_32", kept: Some("cset_ne") },
Compare { name: "cmp_set_ne_ri_64", asks: "cmp_ri_64", kept: Some("cset_ne") },
Compare { name: "cmp_set_lt_ri_32", asks: "cmp_ri_32", kept: Some("cset_lt") },
Compare { name: "cmp_set_lt_ri_64", asks: "cmp_ri_64", kept: Some("cset_lt") },
Compare { name: "cmp_set_le_ri_32", asks: "cmp_ri_32", kept: Some("cset_le") },
Compare { name: "cmp_set_le_ri_64", asks: "cmp_ri_64", kept: Some("cset_le") },
Compare { name: "cmp_set_gt_ri_32", asks: "cmp_ri_32", kept: Some("cset_gt") },
Compare { name: "cmp_set_gt_ri_64", asks: "cmp_ri_64", kept: Some("cset_gt") },
Compare { name: "cmp_set_ge_ri_32", asks: "cmp_ri_32", kept: Some("cset_ge") },
Compare { name: "cmp_set_ge_ri_64", asks: "cmp_ri_64", kept: Some("cset_ge") },
Compare { name: "cmp_set_lo_ri_32", asks: "cmp_ri_32", kept: Some("cset_lo") },
Compare { name: "cmp_set_lo_ri_64", asks: "cmp_ri_64", kept: Some("cset_lo") },
Compare { name: "cmp_set_ls_ri_32", asks: "cmp_ri_32", kept: Some("cset_ls") },
Compare { name: "cmp_set_ls_ri_64", asks: "cmp_ri_64", kept: Some("cset_ls") },
Compare { name: "cmp_set_hi_ri_32", asks: "cmp_ri_32", kept: Some("cset_hi") },
Compare { name: "cmp_set_hi_ri_64", asks: "cmp_ri_64", kept: Some("cset_hi") },
Compare { name: "cmp_set_hs_ri_32", asks: "cmp_ri_32", kept: Some("cset_hs") },
Compare { name: "cmp_set_hs_ri_64", asks: "cmp_ri_64", kept: Some("cset_hs") },
Compare { name: "cmp_rr_32", asks: "cmp_rr_32", kept: None },
Compare { name: "cmp_rr_64", asks: "cmp_rr_64", kept: None },
Compare { name: "cmp_ri_32", asks: "cmp_ri_32", kept: None },
Compare { name: "cmp_ri_64", asks: "cmp_ri_64", kept: None },
];
static READERS: [Reader; 85] = [
Reader { name: "cmp_set_eq_32", reads: Reads::Zero },
Reader { name: "cmp_set_eq_64", reads: Reads::Zero },
Reader { name: "cmp_set_ne_32", reads: Reads::Zero },
Reader { name: "cmp_set_ne_64", reads: Reads::Zero },
Reader { name: "cmp_set_lt_32", reads: Reads::Signed },
Reader { name: "cmp_set_lt_64", reads: Reads::Signed },
Reader { name: "cmp_set_le_32", reads: Reads::Signed },
Reader { name: "cmp_set_le_64", reads: Reads::Signed },
Reader { name: "cmp_set_gt_32", reads: Reads::Signed },
Reader { name: "cmp_set_gt_64", reads: Reads::Signed },
Reader { name: "cmp_set_ge_32", reads: Reads::Signed },
Reader { name: "cmp_set_ge_64", reads: Reads::Signed },
Reader { name: "cmp_set_lo_32", reads: Reads::Unsigned },
Reader { name: "cmp_set_lo_64", reads: Reads::Unsigned },
Reader { name: "cmp_set_ls_32", reads: Reads::Unsigned },
Reader { name: "cmp_set_ls_64", reads: Reads::Unsigned },
Reader { name: "cmp_set_hi_32", reads: Reads::Unsigned },
Reader { name: "cmp_set_hi_64", reads: Reads::Unsigned },
Reader { name: "cmp_set_hs_32", reads: Reads::Unsigned },
Reader { name: "cmp_set_hs_64", reads: Reads::Unsigned },
Reader { name: "cmp_set_eq_ri_32", reads: Reads::Zero },
Reader { name: "cmp_set_eq_ri_64", reads: Reads::Zero },
Reader { name: "cmp_set_ne_ri_32", reads: Reads::Zero },
Reader { name: "cmp_set_ne_ri_64", reads: Reads::Zero },
Reader { name: "cmp_set_lt_ri_32", reads: Reads::Signed },
Reader { name: "cmp_set_lt_ri_64", reads: Reads::Signed },
Reader { name: "cmp_set_le_ri_32", reads: Reads::Signed },
Reader { name: "cmp_set_le_ri_64", reads: Reads::Signed },
Reader { name: "cmp_set_gt_ri_32", reads: Reads::Signed },
Reader { name: "cmp_set_gt_ri_64", reads: Reads::Signed },
Reader { name: "cmp_set_ge_ri_32", reads: Reads::Signed },
Reader { name: "cmp_set_ge_ri_64", reads: Reads::Signed },
Reader { name: "cmp_set_lo_ri_32", reads: Reads::Unsigned },
Reader { name: "cmp_set_lo_ri_64", reads: Reads::Unsigned },
Reader { name: "cmp_set_ls_ri_32", reads: Reads::Unsigned },
Reader { name: "cmp_set_ls_ri_64", reads: Reads::Unsigned },
Reader { name: "cmp_set_hi_ri_32", reads: Reads::Unsigned },
Reader { name: "cmp_set_hi_ri_64", reads: Reads::Unsigned },
Reader { name: "cmp_set_hs_ri_32", reads: Reads::Unsigned },
Reader { name: "cmp_set_hs_ri_64", reads: Reads::Unsigned },
Reader { name: "cset_eq", reads: Reads::Zero },
Reader { name: "cset_ne", reads: Reads::Zero },
Reader { name: "cset_lt", reads: Reads::Signed },
Reader { name: "cset_le", reads: Reads::Signed },
Reader { name: "cset_gt", reads: Reads::Signed },
Reader { name: "cset_ge", reads: Reads::Signed },
Reader { name: "cset_lo", reads: Reads::Unsigned },
Reader { name: "cset_ls", reads: Reads::Unsigned },
Reader { name: "cset_hi", reads: Reads::Unsigned },
Reader { name: "cset_hs", reads: Reads::Unsigned },
Reader { name: "cset_mi", reads: Reads::Bit },
Reader { name: "csel_eq_32", reads: Reads::Zero },
Reader { name: "csel_eq_64", reads: Reads::Zero },
Reader { name: "csel_ne_32", reads: Reads::Zero },
Reader { name: "csel_ne_64", reads: Reads::Zero },
Reader { name: "csel_lt_32", reads: Reads::Signed },
Reader { name: "csel_lt_64", reads: Reads::Signed },
Reader { name: "csel_le_32", reads: Reads::Signed },
Reader { name: "csel_le_64", reads: Reads::Signed },
Reader { name: "csel_gt_32", reads: Reads::Signed },
Reader { name: "csel_gt_64", reads: Reads::Signed },
Reader { name: "csel_ge_32", reads: Reads::Signed },
Reader { name: "csel_ge_64", reads: Reads::Signed },
Reader { name: "csel_lo_32", reads: Reads::Unsigned },
Reader { name: "csel_lo_64", reads: Reads::Unsigned },
Reader { name: "csel_ls_32", reads: Reads::Unsigned },
Reader { name: "csel_ls_64", reads: Reads::Unsigned },
Reader { name: "csel_hi_32", reads: Reads::Unsigned },
Reader { name: "csel_hi_64", reads: Reads::Unsigned },
Reader { name: "csel_hs_32", reads: Reads::Unsigned },
Reader { name: "csel_hs_64", reads: Reads::Unsigned },
Reader { name: "b_eq", reads: Reads::Zero },
Reader { name: "b_ne", reads: Reads::Zero },
Reader { name: "b_lt", reads: Reads::Signed },
Reader { name: "b_le", reads: Reads::Signed },
Reader { name: "b_gt", reads: Reads::Signed },
Reader { name: "b_ge", reads: Reads::Signed },
Reader { name: "b_lo", reads: Reads::Unsigned },
Reader { name: "b_ls", reads: Reads::Unsigned },
Reader { name: "b_hi", reads: Reads::Unsigned },
Reader { name: "b_hs", reads: Reads::Unsigned },
Reader { name: "b_mi", reads: Reads::Bit },
Reader { name: "b_pl", reads: Reads::Bit },
Reader { name: "b_vs", reads: Reads::Bit },
Reader { name: "b_vc", reads: Reads::Bit },
];
static INT_ARGS: [PhysReg; 8] = [x(0), x(1), x(2), x(3), x(4), x(5), x(6), x(7)];
static FP_ARGS: [PhysReg; 8] = [v(0), v(1), v(2), v(3), v(4), v(5), v(6), v(7)];
static INT_RETURNS: [PhysReg; 2] = [x(0), x(1)];
static FP_RETURNS: [PhysReg; 4] = [v(0), v(1), v(2), v(3)];
static NO_X87: [PhysReg; 0] = [];
static INT_SAVED: [PhysReg; 10] =
[x(19), x(20), x(21), x(22), x(23), x(24), x(25), x(26), x(27), x(28)];
static FP_SAVED: [PhysReg; 8] = [v(8), v(9), v(10), v(11), v(12), v(13), v(14), v(15)];
static INT_ORDER: [PhysReg; 26] = [
x(0),
x(1),
x(2),
x(3),
x(4),
x(5),
x(6),
x(7),
x(8),
x(9),
x(10),
x(11),
x(12),
x(13),
x(14),
x(15),
x(19),
x(20),
x(21),
x(22),
x(23),
x(24),
x(25),
x(26),
x(27),
x(28),
];
static FP_ORDER: [PhysReg; 32] = [
v(0),
v(1),
v(2),
v(3),
v(4),
v(5),
v(6),
v(7),
v(16),
v(17),
v(18),
v(19),
v(20),
v(21),
v(22),
v(23),
v(24),
v(25),
v(26),
v(27),
v(28),
v(29),
v(30),
v(31),
v(8),
v(9),
v(10),
v(11),
v(12),
v(13),
v(14),
v(15),
];
pub static AAPCS64: CallRegs = aapcs64(&rucc_abi::abis::AAPCS64, 0, crate::VaList::Aapcs);
pub static DARWIN: CallRegs =
aapcs64(&rucc_abi::abis::DARWIN_ARM64, 128, crate::VaList::CharPointer);
const fn aapcs64(
abi: &'static rucc_abi::AbiDescription,
red_zone: u32,
list: crate::VaList,
) -> CallRegs {
CallRegs {
abi,
int_class: GPR,
sse_class: FPR,
int_args: &INT_ARGS,
sse_args: &FP_ARGS,
shared_positions: false,
int_returns: &INT_RETURNS,
sse_returns: &FP_RETURNS,
x87_returns: &NO_X87,
int_saved: &INT_SAVED,
sse_saved: &FP_SAVED,
int_order: &INT_ORDER,
sse_order: &FP_ORDER,
stack_pointer: SP,
frame_pointer: FP,
late_frame_pointer: false,
vector_count: None,
red_zone,
shadow: 0,
stack_align: 16,
return_address: 0,
word: 8,
push: 16,
link: Some(LR),
sret: Some(X8),
list,
dwarf: &AARCH64_DWARF,
dwarf_return_address: DWARF_RETURN_ADDRESS,
guard: None,
trace: None,
chkstk: None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_file_has_one_name_per_register() {
assert_eq!(REGS.duplicate(), None);
assert_eq!(REGS.len(GPR), 32);
assert_eq!(REGS.len(FPR), 32);
assert_eq!(REGS.name(GPR, SP), Some("sp"));
assert_eq!(REGS.name(GPR, LR), Some("x30"));
assert_eq!(REGS.reg_named("v31"), Some((FPR, v(31))));
}
#[test]
fn dwarf_numbers_are_the_machine_numbers_and_the_vectors_start_at_sixty_four() {
for regs in [&AAPCS64, &DARWIN] {
assert_eq!(regs.dwarf(GPR, x(0)), Some(0));
assert_eq!(regs.dwarf(GPR, SP), Some(31));
assert_eq!(regs.dwarf(FPR, v(8)), Some(72));
assert_eq!(regs.machine(FPR, 95), Some(v(31)));
assert_eq!(regs.dwarf_return_address, 30);
}
}
#[test]
fn nothing_with_a_job_is_handed_out() {
for reserved in [X16, X17, X18, FP, LR, SP] {
assert!(!AAPCS64.int_order.contains(&reserved), "{reserved:?}");
}
for reg in AAPCS64.int_saved {
assert!(AAPCS64.int_order.contains(reg), "{reg:?}");
}
assert_eq!(AAPCS64.int_order.len(), 26);
assert_eq!(AAPCS64.sse_order.len(), 32);
}
#[test]
fn the_two_conventions_differ_in_the_red_zone_and_the_description() {
assert_eq!(AAPCS64.red_zone, 0);
assert_eq!(DARWIN.red_zone, 128);
assert!(std::ptr::eq(AAPCS64.abi, &rucc_abi::abis::AAPCS64));
assert!(std::ptr::eq(DARWIN.abi, &rucc_abi::abis::DARWIN_ARM64));
assert_eq!(AAPCS64.int_args, DARWIN.int_args);
assert_eq!(AAPCS64.return_address, 0);
}
fn described(name: &str) -> bool {
form(name).is_some()
}
#[test]
fn every_name_a_description_hands_out_is_an_opcode() {
let mut names = vec![FRAME.push, FRAME.pop, FRAME.add, FRAME.sub, FRAME.grow, FRAME.align];
names.extend([FRAME.imm, FRAME.lea, FRAME.sum, FRAME.ret, FRAME.differ, FRAME.above]);
names.extend([FRAME.call, PROBE.inst, FRAME.pad.expect("a pad")]);
for moves in FRAME.classes {
names.extend([moves.mov, moves.load, moves.store]);
}
names.extend([BRANCH.cond, BRANCH.test, BRANCH.if_true, BRANCH.if_false, BRANCH.jump]);
names.push(BRANCH.indirect);
names.extend(BRANCH.conditional);
for entry in COMPARES {
names.push(entry.asks);
names.extend(entry.kept);
}
names.extend(READERS.iter().map(|entry| entry.name));
for name in names {
assert!(described(name), "{name} is not an opcode");
}
assert_eq!(FRAME.classes.len(), 2, "both files are spilled");
}
#[test]
fn a_frame_is_taken_in_steps_one_sub_can_carry() {
assert_eq!(FRAME.steps(4080), [4080]);
assert_eq!(FRAME.steps(4096), [4096]);
assert_eq!(FRAME.steps(4608), [4096, 512]);
assert_eq!(FRAME.steps(70016), [69632, 384]);
assert_eq!(FRAME.steps(0x0100_0010), [0x00ff_f000, 0x1000, 0x10]);
for bytes in [16, 4608, 70016, 0x0100_0010] {
let steps = FRAME.steps(bytes);
assert_eq!(steps.iter().sum::<u32>(), bytes);
for step in steps {
let fits = step < 4096 || (step % 4096 == 0 && step >> 12 < 4096);
assert!(fits, "{step} is not a step one sub can carry");
}
}
}
#[test]
fn how_far_from_the_stack_pointer_an_instruction_reaches_is_the_encoders_answer() {
let reaches = FRAME.reaches.unwrap();
assert!(reaches("lea_64", 4095));
assert!(reaches("lea_64", 4096));
assert!(!reaches("lea_64", 5008));
assert!(reaches("ldr_64", 32760));
assert!(!reaches("ldr_64", 32768));
assert!(reaches("ldr_64", -256));
assert!(!reaches("ldr_64", -264));
assert!(reaches("ldr_8", 4095));
assert!(!reaches("ldr_8", 4096));
assert!(reaches("ret", 1 << 20));
}
#[test]
fn every_comparison_folds_into_a_branch_and_its_opposite() {
let sets: Vec<&str> = INSTS
.iter()
.filter(|&&(_, shape)| matches!(shape, Form::CmpSet | Form::CmpSetI))
.map(|&(name, _)| name)
.collect();
let entries: Vec<&str> = BRANCH.fused.iter().map(|fusion| fusion.set).collect();
assert_eq!(entries, sets);
for fusion in BRANCH.fused {
let before = form(fusion.set).expect("described").operands();
let after = form(fusion.cmp).expect("described").operands();
assert_eq!(after, &before[1..], "{} and {}", fusion.set, fusion.cmp);
assert_eq!(form(fusion.if_true), Some(Form::Jcc));
assert_eq!(form(fusion.if_false), Some(Form::Jcc));
let back = BRANCH
.fused
.iter()
.find(|other| other.if_true == fusion.if_false && other.cmp == fusion.cmp)
.unwrap_or_else(|| panic!("{} has no opposite", fusion.if_false));
assert_eq!(back.if_false, fusion.if_true);
let asked = fusion.if_true.strip_prefix("b_").expect("a branch");
assert!(fusion.set.starts_with(&format!("cmp_set_{asked}_")), "{}", fusion.set);
}
assert_eq!(BRANCH.conditional.len(), 14);
}
#[test]
fn every_select_has_the_csel_that_reads_each_condition() {
for entry in BRANCH.moves {
let before = form(entry.select).expect("described").operands();
let after = form(entry.cmov).expect("described").operands();
assert_eq!(form(entry.cmov), Some(Form::Csel));
assert_eq!(after, &before[..before.len() - 1], "{} and {}", entry.select, entry.cmov);
let asked = entry.when.strip_prefix("b_").expect("a branch");
assert!(entry.cmov.starts_with(&format!("csel_{asked}_")), "{}", entry.cmov);
}
assert_eq!(BRANCH.moves.len(), 2 * 10);
}
#[test]
fn every_comparison_says_what_it_asks_and_what_is_left_of_it() {
let comparisons: Vec<&str> = INSTS
.iter()
.filter(|&&(_, shape)| {
matches!(shape, Form::CmpSet | Form::CmpSetI | Form::Cmp | Form::CmpI)
})
.map(|&(name, _)| name)
.collect();
let mut entries: Vec<&str> = COMPARES.iter().map(|entry| entry.name).collect();
let mut sorted = comparisons.clone();
entries.sort_unstable();
sorted.sort_unstable();
assert_eq!(entries, sorted);
for entry in &COMPARES {
let operands = form(entry.name).expect("described").operands();
match entry.kept {
Some(kept) => {
assert_eq!(form(kept), Some(Form::Set), "{kept}");
assert_eq!(form(kept).expect("described").operands(), &operands[..1]);
let asks = form(entry.asks).expect("described").operands();
assert_eq!(asks, &operands[1..], "{} and {}", entry.name, entry.asks);
let condition = kept.strip_prefix("cset_").expect("a cset");
assert!(entry.name.starts_with(&format!("cmp_set_{condition}_")));
}
None => assert_eq!(entry.asks, entry.name),
}
assert!(writes_flags(entry.name), "{}", entry.name);
}
}
#[test]
fn every_condition_says_which_part_of_the_state_it_is_about() {
let readers: Vec<&str> = INSTS
.iter()
.filter(|&&(_, shape)| {
matches!(shape, Form::CmpSet | Form::CmpSetI | Form::Set | Form::Csel | Form::Jcc)
})
.map(|&(name, _)| name)
.collect();
let mut entries: Vec<&str> = READERS.iter().map(|entry| entry.name).collect();
let mut sorted = readers.clone();
entries.sort_unstable();
sorted.sort_unstable();
assert_eq!(entries, sorted);
for entry in &READERS {
let condition = entry
.name
.split('_')
.find(|part| part.len() == 2 && !part.starts_with('r'))
.unwrap_or_else(|| panic!("{} has no condition", entry.name));
let want = match condition {
"eq" | "ne" => Reads::Zero,
"lt" | "le" | "gt" | "ge" => Reads::Signed,
"lo" | "ls" | "hi" | "hs" => Reads::Unsigned,
_ => Reads::Bit,
};
assert_eq!(entry.reads, want, "{}", entry.name);
}
}
#[test]
fn arithmetic_leaves_the_condition_state_alone_and_a_comparison_does_not() {
for name in ["add_rr_64", "sub_ri_32", "and_rr_64", "ldr_64", "csel_eq_32", "b_ne"] {
assert!(!writes_flags(name), "{name}");
}
for name in ["cmp_rr_32", "test_64", "sel_32", "fcmp_f64", "fcmp_set_lt_f32", "bl"] {
assert!(writes_flags(name), "{name}");
}
assert!(writes_flags("adds_rr_64"), "a name this target does not have");
assert!(FLAGS.zeroing.is_empty());
}
#[test]
fn a_compare_that_keeps_its_answer_reads_what_it_found() {
for name in ["cmp_set_eq_32", "cmp_set_hs_ri_64", "fcmp_set_lt_f64"] {
assert!(compares_itself(name), "{name}");
}
for name in ["cset_eq", "csel_ne_64", "b_lt", "cmp_rr_64", "sel_32", "adds_rr_64"] {
assert!(!compares_itself(name), "{name}");
}
}
#[test]
fn the_width_of_an_operand_is_the_width_it_is_written_at() {
assert_eq!((BITS.width)("add_rr_32", 0), Some(32));
assert_eq!((BITS.width)("add_rr_64", 2), Some(64));
assert_eq!((BITS.width)("sxtb_64", 0), Some(64));
assert_eq!((BITS.width)("sel_64", 3), Some(32));
assert_eq!((BITS.width)("fadd_f64", 0), None);
assert_eq!((BITS.width)("scvtf_64_f64", 1), Some(64));
assert_eq!((BITS.width)("ldr_64", 0), Some(64));
assert!((BITS.copies_low)("uxtb_32"));
assert!(!(BITS.copies_low)("mov_rr_64"));
}
#[test]
fn a_widening_reads_as_much_of_its_source_as_its_name_says() {
for (name, bits) in [
("sxtb_16", 8),
("sxtb_32", 8),
("sxtb_64", 8),
("uxtb_32", 8),
("uxtb_64", 8),
("sxth_32", 16),
("uxth_64", 16),
("sxtw_64", 32),
("uxtw_64", 32),
] {
assert_eq!((BITS.width)(name, 1), Some(bits), "{name}");
}
assert_eq!((BITS.width)("sxtb_32", 0), Some(32));
}
#[test]
fn a_machine_pass_is_held_to_the_same_table() {
assert!(MACHINE.calls("a64.blr"));
assert!((MACHINE.takes_mem)("probe_64"));
assert!(MACHINE.touches_mem("a64.push_64"));
assert!(!MACHINE.touches_mem("a64.lea_64"));
assert!(MACHINE.has("a64.cset_eq"));
assert!(!MACHINE.has("a64.adds_rr_64"));
assert!(MACHINE.scales(1) && !MACHINE.scales(8));
}
#[test]
fn a_register_named_in_an_asm_statement_is_the_whole_register_at_either_width() {
assert_eq!(named("x19"), Some((x(19), GPR)));
assert_eq!(named("w19"), Some((x(19), GPR)));
assert_eq!(named("lr"), Some((LR, GPR)));
assert_eq!(named("d8"), Some((v(8), FPR)));
assert_eq!(named("q31"), Some((v(31), FPR)));
for wrong in ["sp", "xzr", "wzr", "x31", "v32", "x07", "r1", "x", ""] {
assert_eq!(named(wrong), None, "{wrong}");
}
}
}