use crate::dbgeng::{
Condition, Decoded, Effect, MemoryOperand, Operand, Privilege, RegisterOperand,
};
struct Out {
mnemonic: String,
operands: Vec<Operand>,
effect: Effect,
condition: Option<Condition>,
writes_flags: bool,
privilege: Option<Privilege>,
writes: Vec<RegisterOperand>,
reads: Vec<RegisterOperand>,
}
impl Out {
fn new(mnemonic: &str) -> Self {
Self {
mnemonic: mnemonic.to_string(),
operands: Vec::new(),
effect: Effect::Other,
condition: None,
writes_flags: false,
privilege: None,
writes: Vec::new(),
reads: Vec::new(),
}
}
fn undecoded(space: &str) -> Self {
let mut out = Self::new("");
out.operands.push(Operand::Undecoded(space.to_string()));
out
}
fn effect(mut self, effect: Effect) -> Self {
self.effect = effect;
self
}
fn flags(mut self) -> Self {
self.writes_flags = true;
self
}
fn cond(mut self, condition: Option<Condition>) -> Self {
self.condition = condition;
self
}
fn privileged(mut self, family: Privilege) -> Self {
self.privilege = Some(family);
self
}
fn out_reg(mut self, register: RegisterOperand) -> Self {
self.record_write(®ister);
self.operands.push(Operand::Register(register));
self
}
fn in_reg(mut self, register: RegisterOperand) -> Self {
self.record_read(®ister);
self.operands.push(Operand::Register(register));
self
}
fn inout_reg(mut self, register: RegisterOperand) -> Self {
self.record_read(®ister);
self.record_write(®ister);
self.operands.push(Operand::Register(register));
self
}
fn writes_only(mut self, register: RegisterOperand) -> Self {
self.record_write(®ister);
self
}
fn reads_only(mut self, register: RegisterOperand) -> Self {
self.record_read(®ister);
self
}
fn imm(mut self, value: u64) -> Self {
self.operands.push(Operand::Immediate(value));
self
}
fn target(mut self, address: u64) -> Self {
self.operands.push(Operand::Target(address));
self
}
fn other(mut self, text: String) -> Self {
self.operands.push(Operand::Other(text));
self
}
fn mem(mut self, memory: MemoryOperand) -> Self {
if let Some(base) = &memory.base {
self.record_read(base);
}
if let Some(index) = &memory.index {
self.record_read(index);
}
self.operands.push(Operand::Memory(memory));
self
}
fn record_write(&mut self, register: &RegisterOperand) {
if register.full != ZERO_REGISTER {
self.writes.push(whole(register));
}
}
fn record_read(&mut self, register: &RegisterOperand) {
if register.full != ZERO_REGISTER {
self.reads.push(register.clone());
}
}
}
const ZERO_REGISTER: &str = "xzr";
fn whole(register: &RegisterOperand) -> RegisterOperand {
RegisterOperand {
name: register.full.clone(),
full: register.full.clone(),
width: match register.full.starts_with('v') {
true => 16,
false => 8,
},
}
}
const fn field(word: u32, lo: u32, width: u32) -> u32 {
(word >> lo) & (u32::MAX >> (32 - width))
}
const fn sign_extend(value: u32, bits: u32) -> i64 {
let shift = 32 - bits;
((value << shift) as i32 >> shift) as i64
}
fn gpr(number: u32, wide: bool, sp: bool) -> RegisterOperand {
RegisterOperand {
name: gpr_name(number, wide, sp),
full: gpr_name(number, true, sp),
width: if wide { 8 } else { 4 },
}
}
fn gpr_name(number: u32, wide: bool, sp: bool) -> String {
match (number, sp) {
(31, true) => if wide { "sp" } else { "wsp" }.to_string(),
(31, false) => if wide { "xzr" } else { "wzr" }.to_string(),
(16, _) => if wide { "xip0" } else { "wip0" }.to_string(),
(17, _) => if wide { "xip1" } else { "wip1" }.to_string(),
(18, _) => if wide { "xpr" } else { "wpr" }.to_string(),
(29, _) => if wide { "fp" } else { "wfp" }.to_string(),
(30, _) => if wide { "lr" } else { "wlr" }.to_string(),
(n, _) => format!("{}{n}", if wide { 'x' } else { 'w' }),
}
}
fn link_register() -> RegisterOperand {
gpr(30, true, false)
}
fn stack_pointer() -> RegisterOperand {
gpr(31, true, true)
}
fn vreg(number: u32, bytes: u32) -> RegisterOperand {
let letter = match bytes {
1 => 'b',
2 => 'h',
4 => 's',
8 => 'd',
_ => 'q',
};
RegisterOperand {
name: format!("{letter}{number}"),
full: format!("v{number}"),
width: bytes,
}
}
fn vreg_whole(number: u32) -> RegisterOperand {
RegisterOperand {
name: format!("v{number}"),
full: format!("v{number}"),
width: 16,
}
}
fn condition(code: u32) -> Option<Condition> {
Some(match code {
0b0000 => Condition::Equal,
0b0001 => Condition::NotEqual,
0b0010 => Condition::UnsignedAboveOrEqual,
0b0011 => Condition::UnsignedBelow,
0b0100 => Condition::Negative,
0b0101 => Condition::NotNegative,
0b0110 => Condition::Overflow,
0b0111 => Condition::NotOverflow,
0b1000 => Condition::UnsignedAbove,
0b1001 => Condition::UnsignedBelowOrEqual,
0b1010 => Condition::SignedGreaterOrEqual,
0b1011 => Condition::SignedLess,
0b1100 => Condition::SignedGreater,
0b1101 => Condition::SignedLessOrEqual,
_ => return None,
})
}
fn condition_suffix(code: u32) -> &'static str {
match code {
0b0000 => "eq",
0b0001 => "ne",
0b0010 => "hs",
0b0011 => "lo",
0b0100 => "mi",
0b0101 => "pl",
0b0110 => "vs",
0b0111 => "vc",
0b1000 => "hi",
0b1001 => "ls",
0b1010 => "ge",
0b1011 => "lt",
0b1100 => "gt",
0b1101 => "le",
0b1110 => "al",
_ => "nv",
}
}
fn invert(code: u32) -> u32 {
code ^ 1
}
fn bitmask_immediate(n: u32, immr: u32, imms: u32, wide: bool) -> Option<u64> {
let datasize = if wide { 64 } else { 32 };
let combined = (n << 6) | ((!imms) & 0x3f);
let len = 31 - combined.leading_zeros() as i32;
if len < 1 || (!wide && n == 1) {
return None;
}
let esize = 1u32 << len;
let levels = esize - 1;
let s = imms & levels;
let r = immr & levels;
if s == levels {
return None;
}
let mut element: u64 = if s + 1 >= 64 {
u64::MAX
} else {
(1u64 << (s + 1)) - 1
};
if r != 0 {
element = ((element >> r) | (element << (esize - r))) & mask_of(esize);
}
let mut value = 0u64;
let mut at = 0;
while at < datasize {
value |= element << at;
at += esize;
}
Some(value & mask_of(datasize))
}
const fn mask_of(bits: u32) -> u64 {
match bits >= 64 {
true => u64::MAX,
false => (1u64 << bits) - 1,
}
}
pub(crate) fn decode(word: u32, address: u64) -> Decoded {
let out = match field(word, 25, 4) {
0b0000 => match word & 0xffff_0000 {
0 => Out::new("udf").imm((word & 0xffff) as u64),
_ => Out::undecoded("reserved"),
},
0b0001 | 0b0011 => Out::undecoded("unallocated"),
0b0010 => Out::undecoded("sve"),
0b1000 | 0b1001 => data_processing_immediate(word, address),
0b1010 | 0b1011 => branch_exception_system(word, address),
op0 if op0 & 0b0101 == 0b0100 => loads_and_stores(word, address),
op0 if op0 & 0b0111 == 0b0101 => data_processing_register(word),
_ => simd_and_floating_point(word),
};
Decoded {
mnemonic: out.mnemonic,
operands: out.operands,
flow: super::flow(word, address),
privileged: out.privilege.is_some(),
privilege: out.privilege,
effect: out.effect,
condition: out.condition,
writes_flags: out.writes_flags,
writes: out.writes,
reads: out.reads,
length: Some(super::INSTRUCTION_BYTES),
}
}
fn data_processing_immediate(word: u32, address: u64) -> Out {
match field(word, 23, 3) {
0b000 | 0b001 => pc_relative(word, address),
0b010 => add_subtract_immediate(word),
0b011 => add_subtract_immediate_tags(word),
0b100 => logical_immediate(word),
0b101 => move_wide_immediate(word),
0b110 => bitfield(word),
_ => extract(word),
}
}
fn pc_relative(word: u32, address: u64) -> Out {
let immediate = (field(word, 5, 19) << 2) | field(word, 29, 2);
let page = word & 0x8000_0000 != 0;
let target = match page {
true => (address & !0xfff).wrapping_add((sign_extend(immediate, 21) * 4096) as u64),
false => address.wrapping_add(sign_extend(immediate, 21) as u64),
};
Out::new(if page { "adrp" } else { "adr" })
.out_reg(gpr(field(word, 0, 5), true, false))
.mem(MemoryOperand {
address: Some(target),
..MemoryOperand::default()
})
.effect(Effect::LoadAddress)
}
fn add_subtract_immediate(word: u32) -> Out {
let wide = word & 0x8000_0000 != 0;
let subtract = word & 0x4000_0000 != 0;
let sets_flags = word & 0x2000_0000 != 0;
let shift = field(word, 22, 2);
if shift > 1 {
return Out::undecoded("unallocated");
}
let value = (field(word, 10, 12) as u64) << (shift * 12);
let (rd, rn) = (field(word, 0, 5), field(word, 5, 5));
let destination = gpr(rd, wide, !sets_flags);
let source = gpr(rn, wide, true);
match (sets_flags, subtract, rd, value) {
(true, true, 31, _) => Out::new("cmp")
.in_reg(source)
.imm(value)
.effect(Effect::Compare)
.flags(),
(true, false, 31, _) => Out::new("cmn").in_reg(source).imm(value).flags(),
(false, false, _, 0) if shift == 0 && (rd == 31 || rn == 31) => Out::new("mov")
.out_reg(destination)
.in_reg(source)
.effect(Effect::Move),
_ => {
let out = Out::new(match (subtract, sets_flags) {
(false, false) => "add",
(false, true) => "adds",
(true, false) => "sub",
(true, true) => "subs",
})
.out_reg(destination)
.in_reg(source)
.imm(value)
.effect(match subtract {
true => Effect::Subtract,
false => Effect::Add,
});
match sets_flags {
true => out.flags(),
false => out,
}
}
}
}
fn add_subtract_immediate_tags(word: u32) -> Out {
if word & 0x0040_0000 != 0 {
let wide = word & 0x8000_0000 != 0;
if word & 0x6000_0000 != 0 {
return Out::undecoded("unallocated");
}
let (mnemonic, signed) = match field(word, 18, 2) {
0b00 => ("smax", true),
0b01 => ("umax", false),
0b10 => ("smin", true),
_ => ("umin", false),
};
let literal = field(word, 10, 8);
return Out::new(mnemonic)
.out_reg(gpr(field(word, 0, 5), wide, false))
.in_reg(gpr(field(word, 5, 5), wide, false))
.imm(match signed {
true => sign_extend(literal, 8) as u64,
false => literal as u64,
});
}
if word & 0x8000_0000 == 0 {
return Out::undecoded("unallocated");
}
Out::new(match word & 0x4000_0000 == 0 {
true => "addg",
false => "subg",
})
.out_reg(gpr(field(word, 0, 5), true, true))
.in_reg(gpr(field(word, 5, 5), true, true))
.imm((field(word, 16, 6) as u64) * 16)
.imm(field(word, 10, 4) as u64)
}
fn logical_immediate(word: u32) -> Out {
let wide = word & 0x8000_0000 != 0;
let opc = field(word, 29, 2);
let Some(value) = bitmask_immediate(
field(word, 22, 1),
field(word, 16, 6),
field(word, 10, 6),
wide,
) else {
return Out::undecoded("unallocated");
};
let (rd, rn) = (field(word, 0, 5), field(word, 5, 5));
let source = gpr(rn, wide, false);
let destination = gpr(rd, wide, opc != 0b11);
match (opc, rd, rn) {
(0b01, _, 31) => Out::new("mov")
.out_reg(destination)
.imm(value)
.effect(Effect::Move),
(0b11, 31, _) => Out::new("tst")
.in_reg(source)
.imm(value)
.effect(Effect::Test)
.flags(),
_ => {
let out = Out::new(match opc {
0b00 => "and",
0b01 => "orr",
0b10 => "eor",
_ => "ands",
})
.out_reg(destination)
.in_reg(source)
.imm(value)
.effect(match opc {
0b00 | 0b11 => Effect::BitAnd,
0b01 => Effect::BitOr,
_ => Effect::BitXor,
});
match opc == 0b11 {
true => out.flags(),
false => out,
}
}
}
}
fn move_wide_immediate(word: u32) -> Out {
let wide = word & 0x8000_0000 != 0;
let opc = field(word, 29, 2);
let shift = field(word, 21, 2) * 16;
if opc == 0b01 || (!wide && shift >= 32) {
return Out::undecoded("unallocated");
}
let immediate = (field(word, 5, 16) as u64) << shift;
let destination = gpr(field(word, 0, 5), wide, false);
match opc {
0b00 => Out::new("mov")
.out_reg(destination)
.imm(!immediate & mask_of(if wide { 64 } else { 32 }))
.effect(Effect::Move),
0b10 => Out::new("mov")
.out_reg(destination)
.imm(immediate)
.effect(Effect::Move),
_ => Out::new("movk").inout_reg(destination).imm(immediate),
}
}
fn bitfield(word: u32) -> Out {
let wide = word & 0x8000_0000 != 0;
let opc = field(word, 29, 2);
if field(word, 22, 1) != u32::from(wide) || opc == 0b11 {
return Out::undecoded("unallocated");
}
let (immr, imms) = (field(word, 16, 6), field(word, 10, 6));
let datasize = if wide { 64 } else { 32 };
if immr >= datasize || imms >= datasize {
return Out::undecoded("unallocated");
}
let destination = gpr(field(word, 0, 5), wide, false);
let source = gpr(field(word, 5, 5), wide, false);
let narrow_source = gpr(field(word, 5, 5), false, false);
match opc {
0b00 => match (immr, imms) {
(0, 7) => Out::new("sxtb").out_reg(destination).in_reg(narrow_source),
(0, 15) => Out::new("sxth").out_reg(destination).in_reg(narrow_source),
(0, 31) if wide => Out::new("sxtw")
.out_reg(destination)
.in_reg(narrow_source)
.effect(Effect::MoveSigned),
(_, _) if imms == datasize - 1 => Out::new("asr")
.out_reg(destination)
.in_reg(source)
.imm(immr as u64)
.effect(Effect::ShiftRight),
(_, _) if imms < immr => Out::new("sbfiz")
.out_reg(destination)
.in_reg(source)
.imm(((datasize - immr) % datasize) as u64)
.imm((imms + 1) as u64),
_ => Out::new("sbfx")
.out_reg(destination)
.in_reg(source)
.imm(immr as u64)
.imm((imms - immr + 1) as u64),
},
0b01 => match (imms < immr, field(word, 5, 5)) {
(true, 31) => Out::new("bfc")
.inout_reg(destination)
.imm(((datasize - immr) % datasize) as u64)
.imm((imms + 1) as u64),
(true, _) => Out::new("bfi")
.inout_reg(destination)
.in_reg(source)
.imm(((datasize - immr) % datasize) as u64)
.imm((imms + 1) as u64),
(false, _) => Out::new("bfxil")
.inout_reg(destination)
.in_reg(source)
.imm(immr as u64)
.imm((imms - immr + 1) as u64),
},
_ => match (immr, imms) {
(0, 7) => Out::new("uxtb").out_reg(destination).in_reg(narrow_source),
(0, 15) => Out::new("uxth").out_reg(destination).in_reg(narrow_source),
(0, 31) if wide => Out::new("uxtw")
.out_reg(destination)
.in_reg(narrow_source)
.effect(Effect::Move),
(_, _) if imms == datasize - 1 => Out::new("lsr")
.out_reg(destination)
.in_reg(source)
.imm(immr as u64)
.effect(Effect::ShiftRight),
(_, _) if imms + 1 == immr => Out::new("lsl")
.out_reg(destination)
.in_reg(source)
.imm((datasize - immr) as u64)
.effect(Effect::ShiftLeft),
(_, _) if imms < immr => Out::new("ubfiz")
.out_reg(destination)
.in_reg(source)
.imm(((datasize - immr) % datasize) as u64)
.imm((imms + 1) as u64),
_ => Out::new("ubfx")
.out_reg(destination)
.in_reg(source)
.imm(immr as u64)
.imm((imms - immr + 1) as u64),
},
}
}
fn extract(word: u32) -> Out {
let wide = word & 0x8000_0000 != 0;
let imms = field(word, 10, 6);
if field(word, 29, 2) != 0
|| field(word, 21, 1) != 0
|| field(word, 22, 1) != u32::from(wide)
|| (!wide && imms >= 32)
{
return Out::undecoded("unallocated");
}
let (rn, rm) = (field(word, 5, 5), field(word, 16, 5));
let out = Out::new(if rn == rm { "ror" } else { "extr" })
.out_reg(gpr(field(word, 0, 5), wide, false))
.in_reg(gpr(rn, wide, false));
match rn == rm {
true => out.imm(imms as u64),
false => out.in_reg(gpr(rm, wide, false)).imm(imms as u64),
}
}
fn branch_exception_system(word: u32, address: u64) -> Out {
let (op0, op1) = (field(word, 29, 3), field(word, 22, 4));
match (op0, op1) {
(0b010, op1) if op1 & 0b1000 == 0 => conditional_branch(word, address),
(0b110, op1) if op1 & 0b1100 == 0 => exception(word),
(0b110, 0b0100) => system(word),
(0b110, op1) if op1 & 0b1000 != 0 => branch_register(word),
(op0, _) if op0 & 0b011 == 0 => branch_immediate(word, address),
(op0, op1) if op0 & 0b011 == 0b001 && op1 & 0b1000 == 0 => {
compare_and_branch(word, address)
}
(op0, _) if op0 & 0b011 == 0b001 => test_and_branch(word, address),
_ => Out::undecoded("unallocated"),
}
}
fn conditional_branch(word: u32, address: u64) -> Out {
if field(word, 24, 1) != 0 {
return Out::undecoded("unallocated");
}
let code = field(word, 0, 4);
let stem = match field(word, 4, 1) {
0 => "b",
_ => "bc",
};
Out::new(&format!("{stem}.{}", condition_suffix(code)))
.target(super::relative(address, field(word, 5, 19), 19))
.cond(condition(code))
}
fn branch_immediate(word: u32, address: u64) -> Out {
let link = word & 0x8000_0000 != 0;
let out = Out::new(if link { "bl" } else { "b" }).target(super::relative(
address,
field(word, 0, 26),
26,
));
match link {
true => out.writes_only(link_register()),
false => out,
}
}
fn compare_and_branch(word: u32, address: u64) -> Out {
Out::new(match field(word, 24, 1) {
0 => "cbz",
_ => "cbnz",
})
.in_reg(gpr(field(word, 0, 5), word & 0x8000_0000 != 0, false))
.target(super::relative(address, field(word, 5, 19), 19))
}
fn test_and_branch(word: u32, address: u64) -> Out {
let bit = (field(word, 31, 1) << 5) | field(word, 19, 5);
Out::new(match field(word, 24, 1) {
0 => "tbz",
_ => "tbnz",
})
.in_reg(gpr(field(word, 0, 5), bit >= 32, false))
.imm(bit as u64)
.target(super::relative(address, field(word, 5, 14), 14))
}
fn branch_register(word: u32) -> Out {
let (op2, op3, op4) = (field(word, 16, 5), field(word, 10, 6), field(word, 0, 5));
if op2 != 0b11111 {
return Out::undecoded("unallocated");
}
let key = match op3 {
0b000000 => "",
0b000010 => "a",
0b000011 => "b",
_ => return Out::undecoded("unallocated"),
};
let rn = field(word, 5, 5);
let op4_fixed = match key.is_empty() {
true => 0b00000,
false => 0b11111,
};
let modifier_form = field(word, 21, 4) & 0b1000 != 0;
if modifier_form && key.is_empty() {
return Out::undecoded("unallocated");
}
match field(word, 21, 4) {
opc @ (0b0000 | 0b1000) => {
let modifier = opc == 0b1000;
if !modifier && op4 != op4_fixed {
return Out::undecoded("unallocated");
}
let out = Out::new(&match (key, modifier) {
("", _) => "br".to_string(),
(key, false) => format!("bra{key}z"),
(key, true) => format!("bra{key}"),
})
.in_reg(gpr(rn, true, false));
match modifier {
true => out.in_reg(gpr(op4, true, true)),
false => out,
}
}
opc @ (0b0001 | 0b1001) => {
let modifier = opc == 0b1001;
if !modifier && op4 != op4_fixed {
return Out::undecoded("unallocated");
}
let out = Out::new(&match (key, modifier) {
("", _) => "blr".to_string(),
(key, false) => format!("blra{key}z"),
(key, true) => format!("blra{key}"),
})
.in_reg(gpr(rn, true, false));
let out = match modifier {
true => out.in_reg(gpr(op4, true, true)),
false => out,
};
out.writes_only(link_register())
}
0b0010 => {
if op4 != op4_fixed || (!key.is_empty() && rn != 0b11111) {
return Out::undecoded("unallocated");
}
let out = Out::new(&match key {
"" => "ret".to_string(),
key => format!("reta{key}"),
});
match (key, rn) {
("", 30) => out.reads_only(link_register()),
("", _) => out.in_reg(gpr(rn, true, false)),
_ => out.reads_only(link_register()).reads_only(stack_pointer()),
}
}
0b0100 => {
if rn != 0b11111 || op4 != op4_fixed {
return Out::undecoded("unallocated");
}
let out = Out::new(&match key {
"" => "eret".to_string(),
key => format!("ereta{key}"),
})
.privileged(Privilege::Other);
match key.is_empty() {
true => out,
false => out.reads_only(stack_pointer()),
}
}
0b0101 if key.is_empty() && rn == 0b11111 && op4 == 0 => {
Out::new("drps").privileged(Privilege::Other)
}
_ => Out::undecoded("unallocated"),
}
}
fn exception(word: u32) -> Out {
if field(word, 2, 3) != 0 {
return Out::undecoded("unallocated");
}
let immediate = field(word, 5, 16) as u64;
let (opc, ll) = (field(word, 21, 3), field(word, 0, 2));
match (opc, ll) {
(0b000, 0b01) => Out::new("svc").imm(immediate),
(0b000, 0b10) => Out::new("hvc")
.imm(immediate)
.privileged(Privilege::Virtualization),
(0b000, 0b11) => Out::new("smc")
.imm(immediate)
.privileged(Privilege::Virtualization),
(0b001, 0b00) => Out::new("brk").imm(immediate),
(0b010, 0b00) => Out::new("hlt").imm(immediate),
(0b011, 0b00) => Out::new("tcancel").imm(immediate),
(0b101, level @ 0b01..=0b11) => Out::new(&format!("dcps{level}"))
.imm(immediate)
.privileged(Privilege::Other),
_ => Out::undecoded("unallocated"),
}
}
fn system(word: u32) -> Out {
let load = field(word, 21, 1) != 0;
let (op0, op1) = (field(word, 19, 2), field(word, 16, 3));
let (crn, crm, op2) = (field(word, 12, 4), field(word, 8, 4), field(word, 5, 3));
let rt = field(word, 0, 5);
match (op0, load) {
(0b00, true) => Out::undecoded("unallocated"),
(0b00, false) => match crn {
0b0010 if op1 == 0b011 && rt == 0b11111 => hint((crm << 3) | op2),
0b0011 => barrier(crm, op2),
0b0100 if rt == 0b11111 => pstate(op1, crm, op2),
_ => Out::undecoded("unallocated"),
},
(0b01, _) => {
let (mnemonic, family) = match maintenance(load, crn, crm, op1, op2) {
Some(named) => named,
None if load => ("sysl", Privilege::Other),
None => ("sys", Privilege::Other),
};
let out = Out::new(mnemonic);
let out = match load {
true => out.out_reg(gpr(rt, true, false)),
false => out,
};
let out = out
.imm(op1 as u64)
.other(format!("C{crn}"))
.other(format!("C{crm}"))
.imm(op2 as u64);
let out = match (load, rt) {
(false, 0b11111) => out,
(false, _) => out.in_reg(gpr(rt, true, false)),
(true, _) => out,
};
out.privileged(family)
}
(_, load) => {
let name = format!("s{op0}_{op1}_c{crn}_c{crm}_{op2}");
let out = match load {
true => Out::new("mrs").out_reg(gpr(rt, true, false)).other(name),
false => Out::new("msr").other(name).in_reg(gpr(rt, true, false)),
};
let out = match (load, op0, op1, crn, crm, op2) {
(false, 0b11, 0b011, 0b0100, 0b0010, 0b000) => out.flags(),
_ => out,
};
let family = if interrupt_mask(op0, op1, crn, crm, op2)
|| all_interrupts_mask(op0, op1, crn, crm, op2)
{
Privilege::InterruptFlag
} else if implementation_defined(op0, crn) {
Privilege::ModelSpecificRegister
} else {
Privilege::ControlRegister
};
match privileged_level(op1) || interrupt_mask(op0, op1, crn, crm, op2) {
true => out.privileged(family),
false => out,
}
}
}
}
const fn privileged_level(op1: u32) -> bool {
op1 != 0b011
}
const fn interrupt_mask(op0: u32, op1: u32, crn: u32, crm: u32, op2: u32) -> bool {
op0 == 0b11 && op1 == 0b011 && crn == 0b0100 && crm == 0b0010 && op2 == 0b001
}
const fn all_interrupts_mask(op0: u32, op1: u32, crn: u32, crm: u32, op2: u32) -> bool {
op0 == 0b11 && op1 == 0b000 && crn == 0b0100 && crm == 0b0011 && op2 == 0b000
}
const fn implementation_defined(op0: u32, crn: u32) -> bool {
op0 == 0b11 && matches!(crn, 0b1011 | 0b1111)
}
fn maintenance(
load: bool,
crn: u32,
crm: u32,
op1: u32,
op2: u32,
) -> Option<(&'static str, Privilege)> {
if load {
return None;
}
let (mnemonic, family, allocated): (_, _, u64) = match (crn, crm) {
(0b0111, 0b0001) => ("ic", Privilege::CacheOrTlb, 0x0000_0000_0000_0001),
(0b0111, 0b0100) => ("dc", Privilege::CacheOrTlb, 0x0000_0000_ba00_0000),
(0b0111, 0b0101) => ("ic", Privilege::CacheOrTlb, 0x0000_0000_0200_0001),
(0b0111, 0b0110) => ("dc", Privilege::CacheOrTlb, 0x0000_0000_0000_007e),
(0b0111, 0b1000) => ("at", Privilege::Other, 0x0003_00f3_0000_000f),
(0b0111, 0b1001) => ("at", Privilege::Other, 0x0004_0004_0000_0007),
(0b0111, 0b1010) => ("dc", Privilege::CacheOrTlb, 0x0000_0000_2a00_0054),
(0b0111, 0b1011) => ("dc", Privilege::CacheOrTlb, 0x0000_0000_8300_0000),
(0b0111, 0b1100) => ("dc", Privilege::CacheOrTlb, 0x0000_0000_2a00_0000),
(0b0111, 0b1101) => ("dc", Privilege::CacheOrTlb, 0x0000_0000_2a00_0000),
(0b0111, 0b1110) => ("dc", Privilege::CacheOrTlb, 0x0022_0081_2a00_0054),
(0b0111, 0b1111) => ("dc", Privilege::CacheOrTlb, 0x0000_0000_8100_0022),
(0b1000 | 0b1001, 0b0000) => ("tlbi", Privilege::CacheOrTlb, 0x0000_0066_0000_0000),
(0b1000 | 0b1001, 0b0001) => ("tlbi", Privilege::CacheOrTlb, 0x0033_0073_0000_00af),
(0b1000 | 0b1001, 0b0010) => ("tlbi", Privilege::CacheOrTlb, 0x0022_0026_0000_00aa),
(0b1000 | 0b1001, 0b0011) => ("tlbi", Privilege::CacheOrTlb, 0x0023_0073_0000_00af),
(0b1000 | 0b1001, 0b0100) => ("tlbi", Privilege::CacheOrTlb, 0x0088_00ff_0000_0000),
(0b1000 | 0b1001, 0b0101) => ("tlbi", Privilege::CacheOrTlb, 0x0022_0026_0000_00aa),
(0b1000 | 0b1001, 0b0110) => ("tlbi", Privilege::CacheOrTlb, 0x0022_0026_0000_00aa),
(0b1000 | 0b1001, 0b0111) => ("tlbi", Privilege::CacheOrTlb, 0x0033_0073_0000_00af),
_ => return None,
};
((allocated >> ((op1 << 3) | op2)) & 1 == 1).then_some((mnemonic, family))
}
fn hint(number: u32) -> Out {
let named = |name: &str| Out::new(name);
match number {
0 => named("nop"),
1 => named("yield"),
2 => named("wfe"),
3 => named("wfi"),
4 => named("sev"),
5 => named("sevl"),
6 => named("dgh"),
7 => named("xpaclri")
.writes_only(link_register())
.reads_only(link_register()),
8 | 10 | 12 | 14 => {
let name = match number {
8 => "pacia1716",
10 => "pacib1716",
12 => "autia1716",
_ => "autib1716",
};
named(name)
.writes_only(gpr(17, true, false))
.reads_only(gpr(17, true, false))
.reads_only(gpr(16, true, false))
}
16 => named("esb"),
17 => named("psb").other("csync".to_string()),
18 => named("tsb").other("csync".to_string()),
19 => named("gcsb").other("dsync".to_string()),
20 => named("csdb"),
22 => named("clrbhb"),
24..=31 => {
let name = match number {
24 => "paciaz",
25 => "paciasp",
26 => "pacibz",
27 => "pacibsp",
28 => "autiaz",
29 => "autiasp",
30 => "autibz",
_ => "autibsp",
};
let out = named(name)
.writes_only(link_register())
.reads_only(link_register());
match number % 2 {
1 => out.reads_only(stack_pointer()),
_ => out,
}
}
32 => named("bti"),
34 => named("bti").other("c".to_string()),
36 => named("bti").other("j".to_string()),
38 => named("bti").other("jc".to_string()),
other => named("hint").imm(other as u64),
}
}
fn barrier(crm: u32, op2: u32) -> Out {
let domain = match crm {
0b0001 => "oshld",
0b0010 => "oshst",
0b0011 => "osh",
0b0101 => "nshld",
0b0110 => "nshst",
0b0111 => "nsh",
0b1001 => "ishld",
0b1010 => "ishst",
0b1011 => "ish",
0b1101 => "ld",
0b1110 => "st",
0b1111 => "sy",
_ => "",
};
let named = |name: &str| match domain.is_empty() {
true => Out::new(name).imm(crm as u64),
false => Out::new(name).other(domain.to_string()),
};
match op2 {
0b010 => Out::new("clrex").imm(crm as u64),
0b100 => match crm {
0b0000 => Out::new("ssbb"),
0b0100 => Out::new("pssbb"),
_ => named("dsb"),
},
0b101 => named("dmb"),
0b110 => match crm {
0b1111 => Out::new("isb").other("sy".to_string()),
_ => Out::new("isb").imm(crm as u64),
},
0b111 => Out::new("sb"),
_ => Out::undecoded("unallocated"),
}
}
fn pstate(op1: u32, crm: u32, op2: u32) -> Out {
let field_name = match (op1, op2) {
(0b000, 0b000) => "cfinv",
(0b000, 0b001) => "xaflag",
(0b000, 0b010) => "axflag",
(0b000, 0b011) => "uao",
(0b000, 0b100) => "pan",
(0b000, 0b101) => "spsel",
(0b001, 0b000) => "allint",
(0b011, 0b001) => "ssbs",
(0b011, 0b010) => "dit",
(0b011, 0b100) => "tco",
(0b011, 0b110) => "daifset",
(0b011, 0b111) => "daifclr",
_ => return Out::undecoded("unallocated"),
};
if matches!((op1, op2), (0b000, 0b000..=0b010)) {
return match crm {
0 => Out::new(field_name).flags(),
_ => Out::undecoded("unallocated"),
};
}
let out = Out::new("msr")
.other(field_name.to_string())
.imm(crm as u64);
let privileged = match (op1, op2) {
(0b000, 0b000..=0b010) => false,
(0b011, 0b110 | 0b111) => true,
(op1, _) => privileged_level(op1),
};
let family = match (op1, op2) {
(0b011, 0b110 | 0b111) | (0b001, 0b000) => Privilege::InterruptFlag,
_ => Privilege::ControlRegister,
};
match privileged {
true => out.privileged(family),
false => out,
}
}
struct Access {
stem: &'static str,
suffix: &'static str,
register: u32,
bytes: u32,
load: bool,
prefetch: bool,
vector: bool,
}
fn access_of(size: u32, opc: u32, vector: bool) -> Option<Access> {
if vector {
let scale = ((opc & 0b10) << 1) | size;
if scale > 4 {
return None;
}
return Some(Access {
stem: if opc & 1 == 1 { "ldr" } else { "str" },
suffix: "",
register: 1 << scale,
bytes: 1 << scale,
load: opc & 1 == 1,
prefetch: false,
vector: true,
});
}
let bytes = 1 << size;
let (stem, suffix, register, load, prefetch) = match (size, opc) {
(0..=2, 0b00) => ("str", ["b", "h", ""][size as usize], 4, false, false),
(0..=2, 0b01) => ("ldr", ["b", "h", ""][size as usize], 4, true, false),
(0..=1, 0b10) => ("ldr", ["sb", "sh"][size as usize], 8, true, false),
(0..=1, 0b11) => ("ldr", ["sb", "sh"][size as usize], 4, true, false),
(2, 0b10) => ("ldr", "sw", 8, true, false),
(3, 0b00) => ("str", "", 8, false, false),
(3, 0b01) => ("ldr", "", 8, true, false),
(3, 0b10) => ("prfm", "", 8, false, true),
_ => return None,
};
Some(Access {
stem,
suffix,
register,
bytes,
load,
prefetch,
vector: false,
})
}
fn prefetch_operation(rt: u32) -> String {
let kind = match field(rt, 3, 2) {
0b00 => "PLD",
0b01 => "PLI",
0b10 => "PST",
_ => return format!("#{rt}"),
};
let level = match field(rt, 1, 2) {
0b00 => "L1",
0b01 => "L2",
0b10 => "L3",
_ => return format!("#{rt}"),
};
let policy = match rt & 1 {
0 => "KEEP",
_ => "STRM",
};
format!("{kind}{level}{policy}")
}
fn transfer(number: u32, access: &Access) -> RegisterOperand {
match access.vector {
true => vreg(number, access.register),
false => gpr(number, access.register == 8, false),
}
}
fn transfer_operands(out: Out, rt: u32, access: &Access, memory: MemoryOperand) -> Out {
let out = match (access.prefetch, access.load) {
(true, _) => out.other(prefetch_operation(rt)),
(false, true) => out.out_reg(transfer(rt, access)),
(false, false) => out.in_reg(transfer(rt, access)),
};
let memory = match access.prefetch {
true => MemoryOperand {
size: None,
..memory
},
false => memory,
};
out.mem(memory)
.effect(match (access.prefetch, access.suffix) {
(true, _) => Effect::Other,
(false, "sb" | "sh" | "sw") => Effect::MoveSigned,
(false, _) => Effect::Move,
})
}
fn loads_and_stores(word: u32, address: u64) -> Out {
match word & 0x3f00_0000 {
0x0800_0000 => return load_store_exclusive(word),
0x0c00_0000 | 0x0d00_0000 => return vector_structures(word),
_ => {}
}
if word & 0x3b00_0000 == 0x1800_0000 {
return load_literal(word, address);
}
if matches!(word & 0x3f00_0000, 0x1900_0000 | 0x1d00_0000)
&& field(word, 30, 2) == 0
&& field(word, 21, 1) == 0
&& field(word, 10, 2) == 0b01
{
return memory_operations(word);
}
if word & 0x3f00_0000 == 0x1900_0000 {
return match field(word, 21, 1) {
0 => unscaled_acquire(word),
_ => memory_tags(word),
};
}
match word & 0x3800_0000 {
0x2800_0000 => load_store_pair(word),
0x3800_0000 => load_store_register(word),
_ => Out::undecoded("unallocated"),
}
}
fn unscaled_acquire(word: u32) -> Out {
let (size, opc) = (field(word, 30, 2), field(word, 22, 2));
let Some(access) = access_of(size, opc, false).filter(|access| !access.prefetch) else {
return Out::undecoded("unallocated");
};
if field(word, 10, 2) != 0 {
return Out::undecoded("unallocated");
}
let mnemonic = format!(
"{}{}",
if access.load { "ldapur" } else { "stlur" },
access.suffix
);
let memory = MemoryOperand {
size: Some(access.bytes),
base: Some(gpr(field(word, 5, 5), true, true)),
scale: 1,
displacement: sign_extend(field(word, 12, 9), 9),
..MemoryOperand::default()
};
transfer_operands(Out::new(&mnemonic), field(word, 0, 5), &access, memory)
}
fn memory_operations(word: u32) -> Out {
let tagged = field(word, 26, 1) != 0;
let (op1, op2) = (field(word, 22, 2), field(word, 12, 4));
let (other, rn, rd) = (field(word, 16, 5), field(word, 5, 5), field(word, 0, 5));
let stage = ["p", "m", "e"];
let through = |number: u32| MemoryOperand {
base: Some(gpr(number, true, false)),
scale: 1,
..MemoryOperand::default()
};
if op1 == 0b11 {
let Some(which) = stage.get((op2 >> 2) as usize) else {
return Out::undecoded("unallocated");
};
let suffix = ["", "t", "n", "tn"][(op2 & 0b11) as usize];
let out = Out::new(&format!(
"set{}{which}{suffix}",
if tagged { "g" } else { "" }
))
.mem(through(rd))
.writes_only(gpr(rd, true, false))
.inout_reg(gpr(rn, true, false))
.in_reg(gpr(other, true, false));
return match *which == "p" {
true => out.flags(),
false => out,
};
}
let which = stage[op1 as usize];
let read = ["", "wt", "rt", "t"][(op2 & 0b11) as usize];
let write = ["", "wn", "rn", "n"][(op2 >> 2) as usize];
let out = Out::new(&format!(
"cpy{}{which}{read}{write}",
if tagged { "" } else { "f" }
))
.mem(through(rd))
.writes_only(gpr(rd, true, false))
.mem(through(other))
.writes_only(gpr(other, true, false))
.inout_reg(gpr(rn, true, false));
match op1 == 0b00 {
true => out.flags(),
false => out,
}
}
fn memory_tags(word: u32) -> Out {
if field(word, 30, 2) != 0b11 {
return Out::undecoded("unallocated");
}
let (opc, index) = (field(word, 22, 2), field(word, 10, 2));
let (rn, rt) = (field(word, 5, 5), field(word, 0, 5));
let displacement = sign_extend(field(word, 12, 9), 9) * 16;
let at_index_zero = index == 0b00;
let mnemonic = match (opc, at_index_zero) {
(0b00, true) => "stzgm",
(0b00, false) => "stg",
(0b01, true) => "ldg",
(0b01, false) => "stzg",
(0b10, true) => "stgm",
(0b10, false) => "st2g",
(0b11, true) => "ldgm",
_ => "stz2g",
};
let granule = matches!(mnemonic, "stzgm" | "stgm" | "ldgm");
if granule && displacement != 0 {
return Out::undecoded("unallocated");
}
let loads = matches!(mnemonic, "ldg" | "ldgm");
let tagged = gpr(rt, true, !loads && !granule);
let out = Out::new(mnemonic);
let out = match (loads, mnemonic) {
(true, "ldg") => out.inout_reg(tagged),
(true, _) => out.out_reg(tagged),
(false, _) => out.in_reg(tagged),
};
let out = out.mem(MemoryOperand {
size: match (granule, mnemonic) {
(true, _) => None,
(_, "st2g" | "stz2g") => Some(32),
_ => Some(16),
},
base: Some(gpr(rn, true, true)),
scale: 1,
displacement: match index {
0b01 => 0,
_ => displacement,
},
..MemoryOperand::default()
});
let out = match index {
0b01 => out.other(post_index_amount(displacement)),
_ => out,
};
match index {
0b01 | 0b11 => out.writes_only(gpr(rn, true, true)),
_ => out,
}
}
fn load_literal(word: u32, address: u64) -> Out {
let vector = field(word, 26, 1) != 0;
let opc = field(word, 30, 2);
let displacement = sign_extend(field(word, 5, 19), 19) * 4;
let target = address.wrapping_add(displacement as u64);
let rt = field(word, 0, 5);
let (out, bytes) = match (vector, opc) {
(false, 0b00) => (Out::new("ldr").out_reg(gpr(rt, false, false)), 4),
(false, 0b01) => (Out::new("ldr").out_reg(gpr(rt, true, false)), 8),
(false, 0b10) => (Out::new("ldrsw").out_reg(gpr(rt, true, false)), 4),
(false, _) => (Out::new("prfm").other(prefetch_operation(rt)), 0),
(true, 0b00) => (Out::new("ldr").out_reg(vreg(rt, 4)), 4),
(true, 0b01) => (Out::new("ldr").out_reg(vreg(rt, 8)), 8),
(true, 0b10) => (Out::new("ldr").out_reg(vreg(rt, 16)), 16),
(true, _) => return Out::undecoded("unallocated"),
};
let effect = match opc {
0b10 if !vector => Effect::MoveSigned,
0b11 if !vector => Effect::Other,
_ => Effect::Move,
};
out.mem(MemoryOperand {
size: (bytes != 0).then_some(bytes),
displacement,
address: Some(target),
..MemoryOperand::default()
})
.effect(effect)
}
fn load_store_pair(word: u32) -> Out {
let vector = field(word, 26, 1) != 0;
let opc = field(word, 30, 2);
let index = field(word, 23, 3);
let load = field(word, 22, 1) != 0;
let (rt, rt2, rn) = (field(word, 0, 5), field(word, 10, 5), field(word, 5, 5));
let (scale, register, signed) = match (vector, opc, load) {
(false, 0b00, _) => (2, 4, false),
(false, 0b01, _) if index == 0b000 => return Out::undecoded("unallocated"),
(false, 0b01, true) => (2, 8, true),
(false, 0b01, false) => (4, 8, false),
(false, 0b10, _) => (3, 8, false),
(true, 0b00, _) => (2, 4, false),
(true, 0b01, _) => (3, 8, false),
(true, 0b10, _) => (4, 16, false),
_ => return Out::undecoded("unallocated"),
};
let displacement = sign_extend(field(word, 15, 7), 7) * (1 << scale);
let mnemonic = match (index, signed, load, vector, opc) {
(0b000, _, true, _, _) => "ldnp",
(0b000, _, false, _, _) => "stnp",
(_, true, _, _, _) => "ldpsw",
(_, _, false, false, 0b01) => "stgp",
(_, _, true, _, _) => "ldp",
(_, _, false, _, _) => "stp",
};
let one = |number: u32| match vector {
true => vreg(number, register),
false => gpr(number, register == 8, false),
};
let out = Out::new(mnemonic);
let out = match load {
true => out.out_reg(one(rt)).out_reg(one(rt2)),
false => out.in_reg(one(rt)).in_reg(one(rt2)),
};
let out = out.mem(MemoryOperand {
size: Some(if signed { 8 } else { register * 2 }),
base: Some(gpr(rn, true, true)),
scale: 1,
displacement: match index {
0b001 => 0,
_ => displacement,
},
..MemoryOperand::default()
});
let out = match index {
0b001 => out.other(post_index_amount(displacement)),
_ => out,
};
match index {
0b001 | 0b011 => out.writes_only(gpr(rn, true, true)),
_ => out,
}
}
fn load_store_register(word: u32) -> Out {
let vector = field(word, 26, 1) != 0;
let (size, opc) = (field(word, 30, 2), field(word, 22, 2));
let (rn, rt) = (field(word, 5, 5), field(word, 0, 5));
if field(word, 24, 2) == 0b01 {
let Some(access) = access_of(size, opc, vector) else {
return Out::undecoded("unallocated");
};
let scale = access.bytes.trailing_zeros();
let memory = MemoryOperand {
size: Some(access.bytes),
base: Some(gpr(rn, true, true)),
scale: 1,
displacement: ((field(word, 10, 12) as i64) << scale),
..MemoryOperand::default()
};
let mnemonic = format!("{}{}", access.stem, access.suffix);
return transfer_operands(Out::new(&mnemonic), rt, &access, memory);
}
match (field(word, 21, 1), field(word, 10, 2)) {
(1, 0b00) if !vector => atomic_memory_operation(word),
(1, 0b10) => {
let Some(access) = access_of(size, opc, vector) else {
return Out::undecoded("unallocated");
};
let option = field(word, 13, 3);
if option & 0b010 == 0 {
return Out::undecoded("unallocated");
}
let shift = match field(word, 12, 1) {
1 => access.bytes.trailing_zeros(),
_ => 0,
};
let memory = MemoryOperand {
size: Some(access.bytes),
base: Some(gpr(rn, true, true)),
index: Some(gpr(field(word, 16, 5), option & 1 == 1, false)),
scale: 1 << shift,
..MemoryOperand::default()
};
let mnemonic = format!("{}{}", access.stem, access.suffix);
transfer_operands(Out::new(&mnemonic), rt, &access, memory)
}
(1, mode) if mode & 1 == 1 => {
if vector || size != 0b11 {
return Out::undecoded("unallocated");
}
let displacement = sign_extend((field(word, 22, 1) << 9) | field(word, 12, 9), 10) * 8;
let out = Out::new(match field(word, 23, 1) {
0 => "ldraa",
_ => "ldrab",
})
.out_reg(gpr(rt, true, false))
.mem(MemoryOperand {
size: Some(8),
base: Some(gpr(rn, true, true)),
scale: 1,
displacement,
..MemoryOperand::default()
})
.effect(Effect::Move);
match mode & 0b10 {
0b10 => out.writes_only(gpr(rn, true, true)),
_ => out,
}
}
(0, mode) => {
let Some(access) = access_of(size, opc, vector) else {
return Out::undecoded("unallocated");
};
if (access.prefetch && mode != 0b00) || (mode == 0b10 && vector) {
return Out::undecoded("unallocated");
}
let displacement = sign_extend(field(word, 12, 9), 9);
let stem = match (mode, access.prefetch, access.stem) {
(0b00, true, _) => "prfum".to_string(),
(0b00, _, stem) => format!("{}ur", &stem[..stem.len() - 1]),
(0b10, _, "ldr") => "ldtr".to_string(),
(0b10, _, _) => "sttr".to_string(),
(_, _, stem) => stem.to_string(),
};
let memory = MemoryOperand {
size: Some(access.bytes),
base: Some(gpr(rn, true, true)),
scale: 1,
displacement: match mode {
0b01 => 0,
_ => displacement,
},
..MemoryOperand::default()
};
let mnemonic = format!("{stem}{}", access.suffix);
let out = transfer_operands(Out::new(&mnemonic), rt, &access, memory);
let out = match mode {
0b01 => out.other(post_index_amount(displacement)),
_ => out,
};
match mode {
0b01 | 0b11 => out.writes_only(gpr(rn, true, true)),
_ => out,
}
}
_ => Out::undecoded("unallocated"),
}
}
fn atomic_memory_operation(word: u32) -> Out {
let size = field(word, 30, 2);
let (acquire, release) = (field(word, 23, 1), field(word, 22, 1));
let (rs, rn, rt) = (field(word, 16, 5), field(word, 5, 5), field(word, 0, 5));
let (o3, opc) = (field(word, 15, 1), field(word, 12, 3));
let wide = size == 0b11;
let width = match size {
0b00 => "b",
0b01 => "h",
_ => "",
};
let ordering = match (acquire, release) {
(0, 0) => "",
(0, _) => "l",
(_, 0) => "a",
_ => "al",
};
let bytes = 1 << size;
let memory = MemoryOperand {
size: Some(bytes),
base: Some(gpr(rn, true, true)),
scale: 1,
..MemoryOperand::default()
};
if o3 == 1 && opc == 0b100 && rs == 0b11111 && acquire == 1 && release == 0 {
return Out::new(&format!("ldapr{width}"))
.out_reg(gpr(rt, wide, false))
.mem(memory)
.effect(Effect::Move);
}
let operation = match (o3, opc) {
(0, 0b000) => "ldadd",
(0, 0b001) => "ldclr",
(0, 0b010) => "ldeor",
(0, 0b011) => "ldset",
(0, 0b100) => "ldsmax",
(0, 0b101) => "ldsmin",
(0, 0b110) => "ldumax",
(0, 0b111) => "ldumin",
(_, 0b000) => "swp",
_ => return Out::undecoded("unallocated"),
};
if rt == 0b11111 && acquire == 0 && operation != "swp" {
let stem = operation.replace("ld", "st");
return Out::new(&format!("{stem}{ordering}{width}"))
.in_reg(gpr(rs, wide, false))
.mem(memory);
}
Out::new(&format!("{operation}{ordering}{width}"))
.in_reg(gpr(rs, wide, false))
.out_reg(gpr(rt, wide, false))
.mem(memory)
}
fn load_store_exclusive(word: u32) -> Out {
let size = field(word, 30, 2);
let (o2, load, o1, o0) = (
field(word, 23, 1),
field(word, 22, 1) != 0,
field(word, 21, 1),
field(word, 15, 1),
);
let (rs, rt2, rn, rt) = (
field(word, 16, 5),
field(word, 10, 5),
field(word, 5, 5),
field(word, 0, 5),
);
let width = match size {
0b00 => "b",
0b01 => "h",
_ => "",
};
let wide = size == 0b11;
let bytes = 1u32 << size;
let memory = |bytes: u32| MemoryOperand {
size: Some(bytes),
base: Some(gpr(rn, true, true)),
scale: 1,
..MemoryOperand::default()
};
match (o2, o1) {
(0, 0) => match load {
true => Out::new(&format!("ld{}xr{width}", if o0 == 1 { "a" } else { "" }))
.out_reg(gpr(rt, wide, false))
.mem(memory(bytes))
.effect(Effect::Move),
false => Out::new(&format!("st{}xr{width}", if o0 == 1 { "l" } else { "" }))
.out_reg(gpr(rs, false, false))
.in_reg(gpr(rt, wide, false))
.mem(memory(bytes)),
},
(0, 1) if size & 0b10 != 0 => match load {
true => Out::new(&format!("ld{}xp", if o0 == 1 { "a" } else { "" }))
.out_reg(gpr(rt, wide, false))
.out_reg(gpr(rt2, wide, false))
.mem(memory(bytes * 2)),
false => Out::new(&format!("st{}xp", if o0 == 1 { "l" } else { "" }))
.out_reg(gpr(rs, false, false))
.in_reg(gpr(rt, wide, false))
.in_reg(gpr(rt2, wide, false))
.mem(memory(bytes * 2)),
},
(0, _) => {
if rs & 1 != 0 || rt & 1 != 0 {
return Out::undecoded("unallocated");
}
let pair_wide = size & 1 != 0;
let element = if pair_wide { 8 } else { 4 };
Out::new(&format!(
"casp{}{}",
if load { "a" } else { "" },
if o0 == 1 { "l" } else { "" }
))
.inout_reg(gpr(rs, pair_wide, false))
.inout_reg(gpr(rs | 1, pair_wide, false))
.in_reg(gpr(rt, pair_wide, false))
.in_reg(gpr(rt | 1, pair_wide, false))
.mem(memory(element * 2))
}
(_, 0) => match load {
true => Out::new(&format!("ld{}r{width}", if o0 == 1 { "a" } else { "la" }))
.out_reg(gpr(rt, wide, false))
.mem(memory(bytes))
.effect(Effect::Move),
false => Out::new(&format!("st{}r{width}", if o0 == 1 { "l" } else { "ll" }))
.in_reg(gpr(rt, wide, false))
.mem(memory(bytes))
.effect(Effect::Move),
},
_ => Out::new(&format!(
"cas{}{}{width}",
if load { "a" } else { "" },
if o0 == 1 { "l" } else { "" }
))
.inout_reg(gpr(rs, wide, false))
.in_reg(gpr(rt, wide, false))
.mem(memory(bytes)),
}
}
fn structure_element(word: u32, single: bool, replicate: &str) -> i64 {
if !single {
return 8 << field(word, 30, 1);
}
if !replicate.is_empty() {
return 1 << field(word, 10, 2);
}
match field(word, 14, 2) {
0b00 => 1,
0b01 => 2,
_ => 4 << field(word, 10, 1),
}
}
fn vector_structures(word: u32) -> Out {
let post_index = field(word, 23, 1) != 0;
if !post_index && field(word, 16, 5) != 0 {
return Out::undecoded("unallocated");
}
let load = field(word, 22, 1) != 0;
let single = word & 0x3f00_0000 == 0x0d00_0000;
let rn = field(word, 5, 5);
let (count, stem) = match (single, field(word, 12, 4)) {
(false, 0b0000) => (4, "4"),
(false, 0b0010) => (4, "1"),
(false, 0b0100) => (3, "3"),
(false, 0b0110) => (3, "1"),
(false, 0b0111) => (1, "1"),
(false, 0b1000) => (2, "2"),
(false, 0b1010) => (2, "1"),
(false, _) => return Out::undecoded("unallocated"),
(true, _) => (((field(word, 13, 1) << 1) | field(word, 21, 1)) + 1, ""),
};
let replicate = match single && field(word, 14, 2) == 0b11 {
true => "r",
false => "",
};
let mnemonic = match (load, stem) {
(true, "") => format!("ld{count}{replicate}"),
(false, "") => format!("st{count}{replicate}"),
(true, stem) => format!("ld{stem}"),
(false, stem) => format!("st{stem}"),
};
let first = field(word, 0, 5);
let mut out = Out::new(&mnemonic).other(format!(
"{{{}}}",
(0..count)
.map(|step| format!("v{}", (first + step) % 32))
.collect::<Vec<_>>()
.join(", ")
));
let merges = load && single && replicate.is_empty();
for step in 0..count {
let register = vreg_whole((first + step) % 32);
if merges || !load {
out = out.reads_only(register.clone());
}
if load {
out = out.writes_only(register);
}
}
let transferred = (count as i64) * structure_element(word, single, replicate);
let out = out.mem(MemoryOperand {
size: Some(transferred as u32),
base: Some(gpr(rn, true, true)),
scale: 1,
..MemoryOperand::default()
});
let rm = field(word, 16, 5);
let out = match (post_index, rm) {
(true, 0b11111) => out.other(post_index_amount(transferred)),
(true, rm) => out.in_reg(gpr(rm, true, false)),
(false, _) => out,
};
match post_index {
true => out.writes_only(gpr(rn, true, true)),
false => out,
}
}
fn data_processing_register(word: u32) -> Out {
let op2 = field(word, 21, 4);
if field(word, 28, 1) == 0 {
return match (op2 & 0b1000, op2 & 1) {
(0, _) => logical_shifted_register(word),
(_, 0) => add_subtract_shifted_register(word),
(_, _) => add_subtract_extended_register(word),
};
}
match op2 {
0b0000 => match field(word, 10, 6) {
0b000000 => add_subtract_with_carry(word),
op3 if op3 & 0b011111 == 0b000001 => Out::new("rmif")
.in_reg(gpr(field(word, 5, 5), true, false))
.imm(field(word, 15, 6) as u64)
.imm(field(word, 0, 4) as u64)
.flags(),
op3 if op3 & 0b001111 == 0b000010 => Out::new(match field(word, 14, 1) {
0 => "setf8",
_ => "setf16",
})
.in_reg(gpr(field(word, 5, 5), false, false))
.flags(),
_ => Out::undecoded("unallocated"),
},
0b0010 => conditional_compare(word),
0b0100 => conditional_select(word),
0b0110 => match field(word, 30, 1) {
0 => data_processing_two_source(word),
_ => data_processing_one_source(word),
},
op2 if op2 & 0b1000 != 0 => data_processing_three_source(word),
_ => Out::undecoded("unallocated"),
}
}
fn shift_modifier(kind: u32, amount: u32) -> Option<String> {
if amount == 0 {
return None;
}
let name = match kind {
0b00 => "lsl",
0b01 => "lsr",
0b10 => "asr",
_ => "ror",
};
Some(format!("{name} #{amount:#x}"))
}
fn logical_shifted_register(word: u32) -> Out {
let wide = word & 0x8000_0000 != 0;
let opc = field(word, 29, 2);
let negated = field(word, 21, 1) != 0;
let amount = field(word, 10, 6);
if !wide && amount >= 32 {
return Out::undecoded("unallocated");
}
let modifier = shift_modifier(field(word, 22, 2), amount);
let (rd, rn, rm) = (field(word, 0, 5), field(word, 5, 5), field(word, 16, 5));
let (destination, left, right) = (
gpr(rd, wide, false),
gpr(rn, wide, false),
gpr(rm, wide, false),
);
if opc == 0b01 && !negated && rn == 31 && field(word, 22, 2) == 0b00 && modifier.is_none() {
return Out::new("mov")
.out_reg(destination)
.in_reg(right)
.effect(Effect::Move);
}
if opc == 0b01 && negated && rn == 31 {
return finish_modifier(
Out::new("mvn").out_reg(destination).in_reg(right),
modifier,
Effect::Other,
);
}
if opc == 0b11 && !negated && rd == 31 {
return finish_modifier(
Out::new("tst").in_reg(left).in_reg(right),
modifier,
Effect::Test,
)
.flags();
}
let mnemonic = match (opc, negated) {
(0b00, false) => "and",
(0b00, true) => "bic",
(0b01, false) => "orr",
(0b01, true) => "orn",
(0b10, false) => "eor",
(0b10, true) => "eon",
(_, false) => "ands",
(_, true) => "bics",
};
let effect = match (opc, negated) {
(_, true) => Effect::Other,
(0b00 | 0b11, _) => Effect::BitAnd,
(0b01, _) => Effect::BitOr,
_ => Effect::BitXor,
};
let out = finish_modifier(
Out::new(mnemonic)
.out_reg(destination)
.in_reg(left)
.in_reg(right),
modifier,
effect,
);
match opc == 0b11 {
true => out.flags(),
false => out,
}
}
fn post_index_amount(displacement: i64) -> String {
match displacement < 0 {
true => format!("#-{:#x}", -displacement),
false => format!("#{displacement:#x}"),
}
}
fn finish_modifier(out: Out, modifier: Option<String>, effect: Effect) -> Out {
match modifier {
Some(text) => out.other(text),
None => out.effect(effect),
}
}
fn add_subtract_shifted_register(word: u32) -> Out {
let wide = word & 0x8000_0000 != 0;
let subtract = word & 0x4000_0000 != 0;
let sets_flags = word & 0x2000_0000 != 0;
let shift = field(word, 22, 2);
let amount = field(word, 10, 6);
if shift == 0b11 || (!wide && amount >= 32) {
return Out::undecoded("unallocated");
}
let modifier = shift_modifier(shift, amount);
let (rd, rn) = (field(word, 0, 5), field(word, 5, 5));
let (destination, left, right) = (
gpr(rd, wide, false),
gpr(rn, wide, false),
gpr(field(word, 16, 5), wide, false),
);
let out = match (sets_flags, subtract, rd, rn) {
(true, true, 31, _) => finish_modifier(
Out::new("cmp").in_reg(left).in_reg(right),
modifier,
Effect::Compare,
),
(true, false, 31, _) => finish_modifier(
Out::new("cmn").in_reg(left).in_reg(right),
modifier,
Effect::Other,
),
(_, true, _, 31) => finish_modifier(
Out::new(if sets_flags { "negs" } else { "neg" })
.out_reg(destination)
.in_reg(right),
modifier,
Effect::Other,
),
_ => finish_modifier(
Out::new(match (subtract, sets_flags) {
(false, false) => "add",
(false, true) => "adds",
(true, false) => "sub",
(true, true) => "subs",
})
.out_reg(destination)
.in_reg(left)
.in_reg(right),
modifier,
match subtract {
true => Effect::Subtract,
false => Effect::Add,
},
),
};
match sets_flags {
true => out.flags(),
false => out,
}
}
fn add_subtract_extended_register(word: u32) -> Out {
let wide = word & 0x8000_0000 != 0;
let subtract = word & 0x4000_0000 != 0;
let sets_flags = word & 0x2000_0000 != 0;
let amount = field(word, 10, 3);
if field(word, 22, 2) != 0 || amount > 4 {
return Out::undecoded("unallocated");
}
let option = field(word, 13, 3);
let (rd, rn) = (field(word, 0, 5), field(word, 5, 5));
let destination = gpr(rd, wide, !sets_flags);
let left = gpr(rn, wide, true);
let right = gpr(field(word, 16, 5), wide && option & 0b011 == 0b011, false);
let plain = amount == 0 && matches!(option, 0b010 | 0b011);
let modifier = match plain {
true => None,
false => Some(format!(
"{} #{amount:#x}",
[
"uxtb", "uxth", "uxtw", "uxtx", "sxtb", "sxth", "sxtw", "sxtx"
][option as usize]
)),
};
let out = match (sets_flags, subtract, rd) {
(true, true, 31) => finish_modifier(
Out::new("cmp").in_reg(left).in_reg(right),
modifier,
Effect::Compare,
),
(true, false, 31) => finish_modifier(
Out::new("cmn").in_reg(left).in_reg(right),
modifier,
Effect::Other,
),
_ => finish_modifier(
Out::new(match (subtract, sets_flags) {
(false, false) => "add",
(false, true) => "adds",
(true, false) => "sub",
(true, true) => "subs",
})
.out_reg(destination)
.in_reg(left)
.in_reg(right),
modifier,
match subtract {
true => Effect::Subtract,
false => Effect::Add,
},
),
};
match sets_flags {
true => out.flags(),
false => out,
}
}
fn add_subtract_with_carry(word: u32) -> Out {
let wide = word & 0x8000_0000 != 0;
let subtract = word & 0x4000_0000 != 0;
let sets_flags = word & 0x2000_0000 != 0;
let rn = field(word, 5, 5);
let destination = gpr(field(word, 0, 5), wide, false);
let right = gpr(field(word, 16, 5), wide, false);
let out = match (subtract, rn) {
(true, 31) => Out::new(if sets_flags { "ngcs" } else { "ngc" })
.out_reg(destination)
.in_reg(right),
_ => Out::new(match (subtract, sets_flags) {
(false, false) => "adc",
(false, true) => "adcs",
(true, false) => "sbc",
(true, true) => "sbcs",
})
.out_reg(destination)
.in_reg(gpr(rn, wide, false))
.in_reg(right)
.effect(match subtract {
true => Effect::Subtract,
false => Effect::Add,
}),
};
match sets_flags {
true => out.flags(),
false => out,
}
}
fn conditional_compare(word: u32) -> Out {
let wide = word & 0x8000_0000 != 0;
let sets_flags = word & 0x2000_0000 != 0;
if !sets_flags || field(word, 10, 1) != 0 || field(word, 4, 1) != 0 {
return Out::undecoded("unallocated");
}
let code = field(word, 12, 4);
let immediate = field(word, 11, 1) != 0;
let out = Out::new(match word & 0x4000_0000 == 0 {
true => "ccmn",
false => "ccmp",
})
.in_reg(gpr(field(word, 5, 5), wide, false));
let out = match immediate {
true => out.imm(field(word, 16, 5) as u64),
false => out.in_reg(gpr(field(word, 16, 5), wide, false)),
};
out.imm(field(word, 0, 4) as u64)
.cond(condition(code))
.flags()
}
fn conditional_select(word: u32) -> Out {
let wide = word & 0x8000_0000 != 0;
let op = field(word, 30, 1);
let op2 = field(word, 10, 2);
if field(word, 29, 1) != 0 || op2 & 0b10 != 0 {
return Out::undecoded("unallocated");
}
let code = field(word, 12, 4);
let (rd, rn, rm) = (field(word, 0, 5), field(word, 5, 5), field(word, 16, 5));
let destination = gpr(rd, wide, false);
let aliasable = code & 0b1110 != 0b1110;
let (base, alias_nullary, alias_unary) = match (op, op2) {
(0, 0) => ("csel", "", ""),
(0, _) => ("csinc", "cset", "cinc"),
(_, 0) => ("csinv", "csetm", "cinv"),
(_, _) => ("csneg", "", "cneg"),
};
if aliasable && !alias_nullary.is_empty() && rn == 31 && rm == 31 {
return Out::new(alias_nullary)
.out_reg(destination)
.cond(condition(invert(code)));
}
if aliasable && !alias_unary.is_empty() && rn == rm {
return Out::new(alias_unary)
.out_reg(destination)
.in_reg(gpr(rn, wide, false))
.cond(condition(invert(code)));
}
Out::new(base)
.out_reg(destination)
.in_reg(gpr(rn, wide, false))
.in_reg(gpr(rm, wide, false))
.cond(condition(code))
}
fn data_processing_two_source(word: u32) -> Out {
let wide = word & 0x8000_0000 != 0;
let sets_flags = word & 0x2000_0000 != 0;
if sets_flags && !(wide && field(word, 10, 6) == 0b000000) {
return Out::undecoded("unallocated");
}
let (mnemonic, effect) = match field(word, 10, 6) {
0b000010 => ("udiv", Effect::Other),
0b000011 => ("sdiv", Effect::Other),
0b001000 => ("lsl", Effect::ShiftLeft),
0b001001 => ("lsr", Effect::ShiftRight),
0b001010 => ("asr", Effect::ShiftRight),
0b001011 => ("ror", Effect::Other),
0b000000 => ("subp", Effect::Other),
0b000100 => ("irg", Effect::Other),
0b000101 => ("gmi", Effect::Other),
0b001100 => ("pacga", Effect::Other),
0b010000 => ("crc32b", Effect::Other),
0b010001 => ("crc32h", Effect::Other),
0b010010 => ("crc32w", Effect::Other),
0b010011 => ("crc32x", Effect::Other),
0b010100 => ("crc32cb", Effect::Other),
0b010101 => ("crc32ch", Effect::Other),
0b010110 => ("crc32cw", Effect::Other),
0b010111 => ("crc32cx", Effect::Other),
0b011000 => ("smax", Effect::Other),
0b011001 => ("umax", Effect::Other),
0b011010 => ("smin", Effect::Other),
0b011011 => ("umin", Effect::Other),
_ => return Out::undecoded("unallocated"),
};
let (destination, left, right) = (
gpr(field(word, 0, 5), wide, matches!(mnemonic, "irg")),
gpr(
field(word, 5, 5),
wide,
matches!(mnemonic, "irg" | "gmi" | "subp"),
),
gpr(
field(word, 16, 5),
wide,
matches!(mnemonic, "subp" | "pacga"),
),
);
let crc = mnemonic.starts_with("crc32");
if sets_flags {
return Out::new("subps")
.out_reg(gpr(field(word, 0, 5), wide, false))
.in_reg(gpr(field(word, 5, 5), wide, true))
.in_reg(gpr(field(word, 16, 5), wide, true))
.flags();
}
let out = Out::new(mnemonic);
match crc {
true => out
.out_reg(gpr(field(word, 0, 5), false, false))
.in_reg(gpr(field(word, 5, 5), false, false))
.in_reg(gpr(field(word, 16, 5), mnemonic.ends_with('x'), false)),
false => out
.out_reg(destination)
.in_reg(left)
.in_reg(right)
.effect(effect),
}
}
fn data_processing_one_source(word: u32) -> Out {
let wide = word & 0x8000_0000 != 0;
if word & 0x2000_0000 != 0 {
return Out::undecoded("unallocated");
}
let (rd, rn) = (field(word, 0, 5), field(word, 5, 5));
let opcode = field(word, 10, 6);
match field(word, 16, 5) {
0b00000 => {
let mnemonic = match (opcode, wide) {
(0b000000, _) => "rbit",
(0b000001, _) => "rev16",
(0b000010, false) => "rev",
(0b000010, true) => "rev32",
(0b000011, true) => "rev",
(0b000100, _) => "clz",
(0b000101, _) => "cls",
(0b000110, _) => "ctz",
(0b000111, _) => "cnt",
(0b001000, _) => "abs",
_ => return Out::undecoded("unallocated"),
};
Out::new(mnemonic)
.out_reg(gpr(rd, wide, false))
.in_reg(gpr(rn, wide, false))
}
0b00001 if wide => {
let mnemonic = match opcode {
0b000000 => "pacia",
0b000001 => "pacib",
0b000010 => "pacda",
0b000011 => "pacdb",
0b000100 => "autia",
0b000101 => "autib",
0b000110 => "autda",
0b000111 => "autdb",
0b001000 => "paciza",
0b001001 => "pacizb",
0b001010 => "pacdza",
0b001011 => "pacdzb",
0b001100 => "autiza",
0b001101 => "autizb",
0b001110 => "autdza",
0b001111 => "autdzb",
0b010000 => "xpaci",
0b010001 => "xpacd",
_ => return Out::undecoded("unallocated"),
};
let out = Out::new(mnemonic).inout_reg(gpr(rd, true, false));
match opcode >= 0b001000 {
true => out,
false => out.in_reg(gpr(rn, true, true)),
}
}
_ => Out::undecoded("unallocated"),
}
}
fn data_processing_three_source(word: u32) -> Out {
let wide = word & 0x8000_0000 != 0;
if word & 0x6000_0000 != 0 {
return Out::undecoded("unallocated");
}
let subtract = field(word, 15, 1) != 0;
let (rd, rn, rm, ra) = (
field(word, 0, 5),
field(word, 5, 5),
field(word, 16, 5),
field(word, 10, 5),
);
let (widening, signed, high) = match field(word, 21, 3) {
0b000 => (false, false, false),
0b001 => (true, true, false),
0b010 if !subtract => (true, true, true),
0b101 => (true, false, false),
0b110 if !subtract => (true, false, true),
_ => return Out::undecoded("unallocated"),
};
if (widening || high) && !wide {
return Out::undecoded("unallocated");
}
if high {
return Out::new(if signed { "smulh" } else { "umulh" })
.out_reg(gpr(rd, true, false))
.in_reg(gpr(rn, true, false))
.in_reg(gpr(rm, true, false));
}
let (destination, accumulator) = (gpr(rd, wide, false), gpr(ra, wide, false));
let source = |number: u32| gpr(number, wide && !widening, false);
let stem = match (widening, signed) {
(false, _) => "",
(true, true) => "s",
(true, false) => "u",
};
if ra == 31 {
let mnemonic = match (widening, subtract) {
(false, false) => "mul".to_string(),
(false, true) => "mneg".to_string(),
(true, false) => format!("{stem}mull"),
(true, true) => format!("{stem}mnegl"),
};
return Out::new(&mnemonic)
.out_reg(destination)
.in_reg(source(rn))
.in_reg(source(rm));
}
let mnemonic = match (widening, subtract) {
(false, false) => "madd".to_string(),
(false, true) => "msub".to_string(),
(true, false) => format!("{stem}maddl"),
(true, true) => format!("{stem}msubl"),
};
Out::new(&mnemonic)
.out_reg(destination)
.in_reg(source(rn))
.in_reg(source(rm))
.in_reg(accumulator)
}
fn simd_and_floating_point(word: u32) -> Out {
if word & 0xbfe0_8400 == 0x0e00_0400 {
return vector_copy(word);
}
if word & 0x5f00_0000 == 0x1e00_0000 {
if field(word, 21, 1) == 0 {
return float_fixed_conversion(word);
}
let op3 = field(word, 10, 6);
if op3 == 0b000000 {
return float_integer_conversion(word);
}
if op3 & 0b001111 == 0b001000 && field(word, 14, 2) == 0 {
return float_compare(word);
}
if op3 & 0b000011 == 0b000001 {
return float_conditional_compare(word);
}
return Out::undecoded("floating-point");
}
match word & 0x5e00_0000 == 0x1e00_0000 {
true => Out::undecoded("floating-point"),
false => Out::undecoded("advanced-simd"),
}
}
fn float_width(kind: u32) -> Option<u32> {
match kind {
0b00 => Some(4),
0b01 => Some(8),
0b11 => Some(2),
_ => None,
}
}
fn float_integer_conversion(word: u32) -> Out {
let wide = word & 0x8000_0000 != 0;
if word & 0x2000_0000 != 0 {
return Out::undecoded("unallocated");
}
let kind = field(word, 22, 2);
let (rmode, opcode) = (field(word, 19, 2), field(word, 16, 3));
let (rd, rn) = (field(word, 0, 5), field(word, 5, 5));
if kind == 0b10 {
if rmode != 0b01 || !wide || !matches!(opcode, 0b110 | 0b111) {
return Out::undecoded("unallocated");
}
return match opcode {
0b110 => Out::new("fmov")
.out_reg(gpr(rd, true, false))
.other(format!("v{rn}.d[1]"))
.reads_only(vreg_whole(rn))
.effect(Effect::Move),
_ => Out::new("fmov")
.other(format!("v{rd}.d[1]"))
.in_reg(gpr(rn, true, false))
.writes_only(vreg_whole(rd))
.reads_only(vreg_whole(rd)),
};
}
let Some(bytes) = float_width(kind) else {
return Out::undecoded("unallocated");
};
let mnemonic = match (rmode, opcode) {
(0b00, 0b000) => "fcvtns",
(0b00, 0b001) => "fcvtnu",
(0b00, 0b010) => "scvtf",
(0b00, 0b011) => "ucvtf",
(0b00, 0b100) => "fcvtas",
(0b00, 0b101) => "fcvtau",
(0b00, 0b110 | 0b111) => "fmov",
(0b01, 0b000) => "fcvtps",
(0b01, 0b001) => "fcvtpu",
(0b10, 0b000) => "fcvtms",
(0b10, 0b001) => "fcvtmu",
(0b11, 0b000) => "fcvtzs",
(0b11, 0b001) => "fcvtzu",
(0b11, 0b110) if kind == 0b01 && !wide => "fjcvtzs",
_ => return Out::undecoded("unallocated"),
};
let out = match matches!(opcode, 0b010 | 0b011 | 0b111) {
true => Out::new(mnemonic)
.out_reg(vreg(rd, bytes))
.in_reg(gpr(rn, wide, false)),
false => Out::new(mnemonic)
.out_reg(gpr(rd, wide, false))
.in_reg(vreg(rn, bytes)),
};
let out = match mnemonic == "fmov" {
true => out.effect(Effect::Move),
false => out,
};
match mnemonic == "fjcvtzs" {
true => out.flags(),
false => out,
}
}
fn float_fixed_conversion(word: u32) -> Out {
let wide = word & 0x8000_0000 != 0;
if word & 0x2000_0000 != 0 {
return Out::undecoded("unallocated");
}
let Some(bytes) = float_width(field(word, 22, 2)) else {
return Out::undecoded("unallocated");
};
let scale = field(word, 10, 6);
if !wide && scale < 32 {
return Out::undecoded("unallocated");
}
let mnemonic = match (field(word, 19, 2), field(word, 16, 3)) {
(0b00, 0b010) => "scvtf",
(0b00, 0b011) => "ucvtf",
(0b11, 0b000) => "fcvtzs",
(0b11, 0b001) => "fcvtzu",
_ => return Out::undecoded("unallocated"),
};
let (rd, rn) = (field(word, 0, 5), field(word, 5, 5));
let fraction = (64 - scale) as u64;
match mnemonic.starts_with("fcvt") {
true => Out::new(mnemonic)
.out_reg(gpr(rd, wide, false))
.in_reg(vreg(rn, bytes))
.imm(fraction),
false => Out::new(mnemonic)
.out_reg(vreg(rd, bytes))
.in_reg(gpr(rn, wide, false))
.imm(fraction),
}
}
fn float_compare(word: u32) -> Out {
if word & 0xa000_0000 != 0 {
return Out::undecoded("unallocated");
}
let Some(bytes) = float_width(field(word, 22, 2)) else {
return Out::undecoded("unallocated");
};
let opcode2 = field(word, 0, 5);
if opcode2 & 0b00111 != 0 {
return Out::undecoded("unallocated");
}
let against_zero = opcode2 & 0b01000 != 0;
let out = Out::new(match opcode2 & 0b10000 {
0 => "fcmp",
_ => "fcmpe",
})
.in_reg(vreg(field(word, 5, 5), bytes));
let out = match against_zero {
true => out.other("#0.0".to_string()),
false => out.in_reg(vreg(field(word, 16, 5), bytes)),
};
out.flags()
}
fn float_conditional_compare(word: u32) -> Out {
if word & 0xa000_0000 != 0 {
return Out::undecoded("unallocated");
}
let Some(bytes) = float_width(field(word, 22, 2)) else {
return Out::undecoded("unallocated");
};
Out::new(match field(word, 4, 1) {
0 => "fccmp",
_ => "fccmpe",
})
.in_reg(vreg(field(word, 5, 5), bytes))
.in_reg(vreg(field(word, 16, 5), bytes))
.imm(field(word, 0, 4) as u64)
.cond(condition(field(word, 12, 4)))
.flags()
}
fn vector_copy(word: u32) -> Out {
let q = field(word, 30, 1) != 0;
let imm5 = field(word, 16, 5);
let (element, index) = match imm5.trailing_zeros() {
0 => ('b', imm5 >> 1),
1 => ('h', imm5 >> 2),
2 => ('s', imm5 >> 3),
3 => ('d', imm5 >> 4),
_ => return Out::undecoded("unallocated"),
};
let (rd, rn) = (field(word, 0, 5), field(word, 5, 5));
let lane = |number: u32| format!("v{number}.{element}[{index}]");
match field(word, 11, 4) {
0b0000 => Out::new("dup")
.other(format!("v{rd}"))
.other(lane(rn))
.writes_only(vreg_whole(rd))
.reads_only(vreg_whole(rn)),
0b0001 => Out::new("dup")
.other(format!("v{rd}"))
.in_reg(gpr(rn, element == 'd', false))
.writes_only(vreg_whole(rd)),
0b0011 => Out::new("ins")
.other(lane(rd))
.in_reg(gpr(rn, element == 'd', false))
.writes_only(vreg_whole(rd))
.reads_only(vreg_whole(rd)),
0b0101 => Out::new("smov")
.out_reg(gpr(rd, q, false))
.other(lane(rn))
.reads_only(vreg_whole(rn))
.effect(Effect::MoveSigned),
0b0111 => Out::new("umov")
.out_reg(gpr(rd, element == 'd', false))
.other(lane(rn))
.reads_only(vreg_whole(rn))
.effect(Effect::Move),
_ => Out::undecoded("advanced-simd"),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dbgeng::Flow;
const ANYWHERE: u64 = 0x1000;
fn shapes(word: u32) -> Decoded {
decode(word, ANYWHERE)
}
fn spellings(registers: &[RegisterOperand]) -> Vec<&str> {
registers.iter().map(|r| r.name.as_str()).collect()
}
fn register(name: &str, full: &str, width: u32) -> Operand {
Operand::Register(RegisterOperand {
name: name.to_string(),
full: full.to_string(),
width,
})
}
#[test]
fn test_a_shifted_immediate_is_folded_into_the_value_it_means() {
let subs = shapes(0x7157_6343);
assert_eq!(subs.mnemonic, "subs");
assert_eq!(subs.operands[2], Operand::Immediate(0x5d8 << 12));
assert_eq!(subs.effect, Effect::Subtract);
assert!(subs.writes_flags);
let cmp = shapes(0x7140_053f);
assert_eq!(cmp.mnemonic, "cmp");
assert_eq!(cmp.operands[1], Operand::Immediate(0x1000));
assert_eq!(shapes(0xf100_10df).operands[1], Operand::Immediate(4));
}
#[test]
fn test_a_compare_writes_nothing_and_a_compare_negative_is_not_one() {
let cmp = shapes(0xf100_10df);
assert_eq!(cmp.operands.len(), 2);
assert_eq!(cmp.effect, Effect::Compare);
assert!(cmp.writes.is_empty(), "{cmp:?}");
assert!(cmp.writes_flags);
let cmn = shapes(0x3100_067f);
assert_eq!(cmn.mnemonic, "cmn");
assert_eq!(cmn.effect, Effect::Other);
assert!(cmn.writes_flags);
assert!(cmn.writes.is_empty(), "{cmn:?}");
}
#[test]
fn test_a_bitmask_immediate_is_decoded_rather_than_read() {
assert_eq!(
shapes(0x9274_ce52).operands[2],
Operand::Immediate(0xffff_ffff_ffff_f000)
);
let wide = shapes(0xb240_7fe4);
assert_eq!(wide.mnemonic, "mov");
assert_eq!(wide.effect, Effect::Move);
assert_eq!(wide.operands[1], Operand::Immediate(0xffff_ffff));
assert_eq!(
shapes(0xb200_03e8).operands[1],
Operand::Immediate(0x1_0000_0001)
);
let tst = shapes(0xf25a_005f);
assert_eq!(tst.mnemonic, "tst");
assert_eq!(tst.effect, Effect::Test);
assert_eq!(tst.operands[1], Operand::Immediate(0x40_0000_0000));
assert!(tst.writes.is_empty(), "{tst:?}");
}
#[test]
fn test_a_move_wide_reports_the_value_it_writes() {
assert_eq!(shapes(0xd280_0016).operands[1], Operand::Immediate(0));
let negative = shapes(0x1281_383a);
assert_eq!(negative.mnemonic, "mov");
assert_eq!(negative.operands[1], Operand::Immediate(0xffff_f63e));
let keep = shapes(0xf2b8_0000);
assert_eq!(keep.mnemonic, "movk");
assert_eq!(keep.operands[1], Operand::Immediate(0xc000_0000));
assert_eq!(spellings(&keep.writes), ["x0"]);
assert_eq!(
spellings(&keep.reads),
["x0"],
"a `movk` keeps the other halves"
);
assert_eq!(keep.effect, Effect::Other, "it is a merge, not a copy");
}
#[test]
fn test_a_bitfield_alias_changes_the_operand_list() {
let lsr = shapes(0x5310_7c00);
assert_eq!(lsr.mnemonic, "lsr");
assert_eq!(lsr.effect, Effect::ShiftRight);
assert_eq!(lsr.operands[2], Operand::Immediate(0x10));
let lsl = shapes(0xd378_dfde);
assert_eq!(lsl.mnemonic, "lsl");
assert_eq!(lsl.effect, Effect::ShiftLeft);
assert_eq!(lsl.operands[2], Operand::Immediate(8));
assert_eq!(shapes(0x9343_fdad).mnemonic, "asr");
let ubfx = shapes(0x5306_5509);
assert_eq!(ubfx.mnemonic, "ubfx");
assert_eq!(ubfx.operands[2], Operand::Immediate(6));
assert_eq!(ubfx.operands[3], Operand::Immediate(0x10));
assert_eq!(ubfx.effect, Effect::Other);
let uxtb = shapes(0x5300_1d00);
assert_eq!(uxtb.mnemonic, "uxtb");
assert_eq!(uxtb.effect, Effect::Other);
assert_eq!(shapes(0xd340_1c20).effect, Effect::Other, "and at 64 bits");
assert_eq!(shapes(0x5300_3c00).effect, Effect::Other, "uxth");
let sxtw = shapes(0x9340_7c59);
assert_eq!(sxtw.mnemonic, "sxtw");
assert_eq!(sxtw.effect, Effect::MoveSigned);
assert_eq!(shapes(0xd340_7c08).effect, Effect::Move, "uxtw");
assert_eq!(sxtw.operands[1], register("w2", "x2", 4));
let bfi = shapes(0xb374_cd28);
assert_eq!(bfi.mnemonic, "bfi");
assert_eq!(spellings(&bfi.reads), ["x8", "x9"]);
assert_eq!(spellings(&bfi.writes), ["x8"]);
}
#[test]
fn test_a_page_address_truncates_the_instruction_s_own() {
let adrp = decode(0x9000_5928, 0xfffff802_e9e5d5b4);
assert_eq!(adrp.mnemonic, "adrp");
assert_eq!(adrp.effect, Effect::LoadAddress);
assert_eq!(
adrp.operands[1],
Operand::Memory(MemoryOperand {
address: Some(0xfffff802_ea981000),
..MemoryOperand::default()
})
);
let adr = decode(0x10ff_ffe1, 0xfffff802_e9e000d0);
assert_eq!(adr.mnemonic, "adr");
assert_eq!(
adr.operands[1],
Operand::Memory(MemoryOperand {
address: Some(0xfffff802_e9e000cc),
..MemoryOperand::default()
})
);
}
#[test]
fn test_a_call_writes_the_link_register_without_naming_it() {
let call = decode(0x97ff_fcc4, 0xfffff802_e9e5df70);
assert_eq!(call.mnemonic, "bl");
assert_eq!(call.operands, [Operand::Target(0xfffff802_e9e5d280)]);
assert_eq!(spellings(&call.writes), ["lr"]);
let indirect = shapes(0xd63f_01e0);
assert_eq!(indirect.mnemonic, "blr");
assert_eq!(spellings(&indirect.reads), ["x15"]);
assert_eq!(spellings(&indirect.writes), ["lr"]);
let ret = shapes(0xd65f_03c0);
assert_eq!(ret.mnemonic, "ret");
assert!(ret.operands.is_empty(), "{ret:?}");
assert_eq!(spellings(&ret.reads), ["lr"]);
assert!(shapes(0x1400_0004).writes.is_empty());
}
#[test]
fn test_a_conditional_instruction_carries_the_condition_as_a_field() {
let branch = decode(0x5400_02c1, 0xfffff802_ea69dc1c);
assert_eq!(branch.mnemonic, "b.ne");
assert_eq!(branch.condition, Some(Condition::NotEqual));
assert_eq!(branch.flow, Flow::Branch(Some(0xfffff802_ea69dc74)));
assert_eq!(
shapes(0x5400_02c3).condition,
Some(Condition::UnsignedBelow)
);
assert_eq!(
shapes(0x5400_028c).condition,
Some(Condition::SignedGreater)
);
let always = shapes(0x5400_002e);
assert_eq!(always.condition, None);
assert_eq!(always.flow, Flow::Jmp(Some(0x1004)));
}
#[test]
fn test_a_conditional_select_alias_inverts_its_condition() {
let set = shapes(0x9a9f_37e0);
assert_eq!(set.mnemonic, "cset");
assert_eq!(set.condition, Some(Condition::UnsignedAboveOrEqual));
assert_eq!(set.operands, [register("x0", "x0", 8)]);
let increment = shapes(0x9a87_24e7);
assert_eq!(increment.mnemonic, "cinc");
assert_eq!(increment.condition, Some(Condition::UnsignedBelow));
let select = shapes(0x9a89_1108);
assert_eq!(select.mnemonic, "csel");
assert_eq!(select.condition, Some(Condition::NotEqual));
assert_eq!(select.operands.len(), 3);
}
#[test]
fn test_a_conditional_compare_is_not_a_compare() {
let ccmp = shapes(0xfa4a_1284);
assert_eq!(ccmp.mnemonic, "ccmp");
assert_eq!(ccmp.effect, Effect::Other);
assert_eq!(ccmp.condition, Some(Condition::NotEqual));
assert!(ccmp.writes_flags);
assert_eq!(spellings(&ccmp.reads), ["x20", "x10"]);
assert_eq!(ccmp.operands[2], Operand::Immediate(4));
let immediate = shapes(0x7a40_0984);
assert_eq!(immediate.operands[1], Operand::Immediate(0));
assert_eq!(immediate.condition, Some(Condition::Equal));
}
#[test]
fn test_a_load_scales_its_displacement_by_the_access_width() {
let wide = shapes(0xf942_1e93);
assert_eq!(wide.mnemonic, "ldr");
assert_eq!(wide.effect, Effect::Move);
assert_eq!(
wide.operands[1],
Operand::Memory(MemoryOperand {
size: Some(8),
base: Some(RegisterOperand {
name: "x20".into(),
full: "x20".into(),
width: 8
}),
scale: 1,
displacement: 0x438,
..MemoryOperand::default()
})
);
let half = shapes(0x7940_0423);
assert_eq!(half.mnemonic, "ldrh");
let Operand::Memory(memory) = &half.operands[1] else {
panic!("{half:?}");
};
assert_eq!((memory.size, memory.displacement), (Some(2), 2));
assert_eq!(shapes(0xb986_8d02).effect, Effect::MoveSigned);
}
#[test]
fn test_an_indexed_access_reports_the_address_it_forms_and_the_base_it_moves() {
let post = shapes(0xb840_4423);
let Operand::Memory(memory) = &post.operands[1] else {
panic!("{post:?}");
};
assert_eq!(memory.displacement, 0, "the access is at the base itself");
assert_eq!(spellings(&post.writes), ["x3", "x1"]);
assert_eq!(
post.operands[2],
Operand::Other("#0x4".to_string()),
"the amount has nowhere else to go: {post:?}"
);
let pre = shapes(0xf840_8d09);
let Operand::Memory(memory) = &pre.operands[1] else {
panic!("{pre:?}");
};
assert_eq!(memory.displacement, 8);
assert_eq!(spellings(&pre.writes), ["x9", "x8"]);
assert_eq!(pre.operands.len(), 2, "a pre-index needs no name: {pre:?}");
let unscaled = shapes(0xf840_3028);
assert_eq!(unscaled.mnemonic, "ldur");
assert!(unscaled.writes.len() == 1, "no writeback: {unscaled:?}");
let byte = shapes(0x385f_02c8);
assert_eq!(byte.mnemonic, "ldurb");
let Operand::Memory(memory) = &byte.operands[1] else {
panic!("{byte:?}");
};
assert_eq!(memory.displacement, -0x10);
}
#[test]
fn test_a_store_names_its_source_first_and_writes_no_register() {
let store = shapes(0xf900_0fe8);
assert_eq!(store.mnemonic, "str");
assert_eq!(store.operands[0], register("x8", "x8", 8));
assert!(matches!(store.operands[1], Operand::Memory(_)));
assert!(store.writes.is_empty(), "{store:?}");
assert_eq!(spellings(&store.reads), ["x8", "sp"]);
}
#[test]
fn test_a_pair_reaches_two_registers_and_its_base() {
let push = shapes(0xa9bf_7bfd);
assert_eq!(push.mnemonic, "stp");
assert_eq!(push.effect, Effect::Other);
assert_eq!(spellings(&push.reads), ["fp", "lr", "sp"]);
assert_eq!(
spellings(&push.writes),
["sp"],
"the writeback, and nothing else"
);
let Operand::Memory(memory) = &push.operands[2] else {
panic!("{push:?}");
};
assert_eq!((memory.size, memory.displacement), (Some(16), -0x10));
let pop = shapes(0xa942_7bfd);
assert_eq!(pop.mnemonic, "ldp");
assert_eq!(spellings(&pop.writes), ["fp", "lr"]);
let unwind = shapes(0xa8c3_53f3);
assert_eq!(spellings(&unwind.writes), ["x19", "x20", "sp"]);
assert_eq!(unwind.operands[3], Operand::Other("#0x30".to_string()));
assert_eq!(
shapes(0xf81f_84df).operands[2],
Operand::Other("#-0x8".to_string())
);
let signed = shapes(0x694d_5050);
assert_eq!(signed.mnemonic, "ldpsw");
assert_eq!(spellings(&signed.writes), ["xip0", "x20"]);
}
#[test]
fn test_a_literal_load_resolves_its_own_address() {
let literal = decode(0x5800_01e5, 0xfffff802_e9e001dc);
assert_eq!(literal.mnemonic, "ldr");
let Operand::Memory(memory) = &literal.operands[1] else {
panic!("{literal:?}");
};
assert_eq!(memory.address, Some(0xfffff802_e9e00218));
assert_eq!(memory.base, None, "nothing at run time contributes to it");
assert_eq!(memory.size, Some(8));
}
#[test]
fn test_the_atomic_family_separates_what_goes_in_from_what_comes_out() {
let add = shapes(0xf828_0128);
assert_eq!(add.mnemonic, "ldadd");
assert_eq!(spellings(&add.reads), ["x8", "x9"]);
assert_eq!(spellings(&add.writes), ["x8"]);
let swap = shapes(0xc8e8_ff20);
assert_eq!(swap.mnemonic, "casal");
assert_eq!(spellings(&swap.reads), ["x8", "x0", "x25"]);
assert_eq!(spellings(&swap.writes), ["x8"]);
let exclusive = shapes(0xc811_7d49);
assert_eq!(exclusive.mnemonic, "stxr");
assert_eq!(spellings(&exclusive.writes), ["xip1"]);
assert_eq!(spellings(&exclusive.reads), ["x9", "x10"]);
let acquire = shapes(0xb8bf_c129);
assert_eq!(acquire.mnemonic, "ldapr");
assert_eq!(acquire.effect, Effect::Move);
assert_eq!(shapes(0x88a8_7c02).mnemonic, "cas");
let pair = shapes(0x482a_7c0c);
assert_eq!(pair.mnemonic, "casp");
assert_eq!(spellings(&pair.writes), ["x10", "x11"]);
}
#[test]
fn test_a_modifier_the_operand_list_cannot_carry_demotes_the_effect() {
let shifted = shapes(0xaa04_e3de);
assert_eq!(shifted.mnemonic, "orr");
assert_eq!(shifted.effect, Effect::Other);
assert_eq!(shifted.operands[3], Operand::Other("lsl #0x38".to_string()));
let plain = shapes(0xaa05_0085);
assert_eq!(plain.effect, Effect::BitOr);
assert_eq!(plain.operands.len(), 3);
assert_eq!(shapes(0xaa01_03e0).mnemonic, "mov");
let not_a_move = shapes(0xaa41_03e0);
assert_eq!(not_a_move.mnemonic, "orr");
assert_eq!(not_a_move.effect, Effect::BitOr);
assert_eq!(not_a_move.operands.len(), 3);
let compare = shapes(0xeb47_311f);
assert_eq!(compare.mnemonic, "cmp");
assert_eq!(compare.effect, Effect::Other);
assert!(compare.writes_flags);
assert_eq!(shapes(0x8a06_00e7).effect, Effect::BitAnd);
let clear = shapes(0x8a22_0021);
assert_eq!(clear.mnemonic, "bic");
assert_eq!(clear.effect, Effect::Other);
}
#[test]
fn test_an_extension_the_register_width_already_says_is_not_a_modifier() {
let widened = shapes(0x8b29_4100);
assert_eq!(widened.effect, Effect::Add);
assert_eq!(widened.operands[2], register("w9", "x9", 4));
assert_eq!(widened.operands.len(), 3);
let stack = shapes(0xcb23_63ff);
assert_eq!(stack.effect, Effect::Subtract);
assert_eq!(stack.operands[0], register("sp", "sp", 8));
let narrowed = shapes(0x0b23_2000);
assert_eq!(narrowed.effect, Effect::Other);
assert_eq!(
narrowed.operands[3],
Operand::Other("uxth #0x0".to_string())
);
assert_eq!(shapes(0xeb20_c27f).effect, Effect::Other);
assert_eq!(shapes(0x0b22_6020).operands[2], register("w2", "x2", 4));
assert_eq!(shapes(0x8b22_6020).operands[2], register("x2", "x2", 8));
}
#[test]
fn test_a_multiply_accumulating_nothing_is_written_as_one() {
let multiply = shapes(0x9b09_7d08);
assert_eq!(multiply.mnemonic, "mul");
assert_eq!(multiply.operands.len(), 3);
let accumulate = shapes(0x9b0b_3108);
assert_eq!(accumulate.mnemonic, "madd");
assert_eq!(accumulate.operands.len(), 4);
let widening = shapes(0x9ba9_7d49);
assert_eq!(widening.mnemonic, "umull");
assert_eq!(widening.operands[1], register("w10", "x10", 4));
assert_eq!(widening.operands[0], register("x9", "x9", 8));
assert_eq!(shapes(0x9bc7_7ccc).mnemonic, "umulh");
assert_eq!(shapes(0x9ac1_0928).mnemonic, "udiv");
let variable = shapes(0x9ac5_24c6);
assert_eq!(variable.mnemonic, "lsr");
assert_eq!(variable.effect, Effect::ShiftRight);
assert_eq!(shapes(0x5ac0_1108).mnemonic, "clz");
assert_eq!(shapes(0xdac0_0d09).mnemonic, "rev");
}
#[test]
fn test_a_system_register_is_named_and_gated_by_its_encoding() {
let write = shapes(0xd518_2025);
assert_eq!(write.mnemonic, "msr");
assert_eq!(
write.operands[0],
Operand::Other("s3_0_c2_c0_1".to_string())
);
assert_eq!(spellings(&write.reads), ["x5"]);
assert!(write.privileged);
let counter = shapes(0xd53b_e04d);
assert_eq!(counter.mnemonic, "mrs");
assert_eq!(
counter.operands[1],
Operand::Other("s3_3_c14_c0_2".to_string())
);
assert!(!counter.privileged);
assert_eq!(spellings(&counter.writes), ["x13"]);
assert!(!shapes(0xd53b_4208).privileged);
assert!(!shapes(0xd51b_4402).privileged);
let flags = shapes(0xd51b_4208);
assert_eq!(flags.mnemonic, "msr");
assert!(flags.writes_flags, "{flags:?}");
}
#[test]
fn test_the_interrupt_mask_is_privileged_though_its_field_names_el0() {
let mask = shapes(0xd503_41df);
assert_eq!(mask.mnemonic, "msr");
assert_eq!(mask.operands[0], Operand::Other("daifset".to_string()));
assert_eq!(mask.operands[1], Operand::Immediate(1));
assert!(mask.privileged);
assert!(shapes(0xd503_42ff).privileged);
assert!(shapes(0xd500_40bf).privileged);
assert!(shapes(0xd51b_4220).privileged, "msr DAIF");
assert!(shapes(0xd53b_4221).privileged, "mrs DAIF");
assert!(!shapes(0xd53b_4208).privileged, "mrs NZCV");
let restore = shapes(0xd51b_4208);
assert!(!restore.privileged, "msr NZCV");
assert!(
restore.writes_flags,
"and it is the one that sets the flags"
);
}
#[test]
fn test_the_maintenance_operations_are_named_from_their_register_space() {
let tlb = shapes(0xd508_871f);
assert_eq!(tlb.mnemonic, "tlbi");
assert!(tlb.privileged);
let clean = shapes(0xd50b_7a29);
assert_eq!(clean.mnemonic, "dc");
assert!(clean.privileged);
assert_eq!(spellings(&clean.reads), ["x9"]);
for word in [0xd50b_7b20_u32, 0xd50b_7521, 0xd50b_7420] {
assert!(shapes(word).privileged, "{word:#010x}");
}
assert!(!shapes(0xd503_3fdf).privileged);
assert!(!shapes(0xd503_201f).privileged);
assert_eq!(shapes(0xd508_751f).mnemonic, "ic");
assert!(shapes(0xd508_751f).privileged);
assert_eq!(shapes(0xd508_7855).mnemonic, "at");
let generic = shapes(0xd50b_7388);
assert_eq!(generic.mnemonic, "sys");
assert_eq!(
generic.operands[..4],
[
Operand::Immediate(3),
Operand::Other("C7".to_string()),
Operand::Other("C3".to_string()),
Operand::Immediate(4),
]
);
}
#[test]
fn test_a_privileged_instruction_says_which_family_it_reaches() {
use Privilege::*;
for (word, text, family) in [
(0xd518_2025_u32, "msr TTBR1_EL1,x5", Some(ControlRegister)),
(0xd510_0240, "msr MDSCR_EL1,x0", Some(ControlRegister)),
(0xd51b_4220, "msr DAIF,x0", Some(InterruptFlag)),
(0xd53b_4221, "mrs x1,DAIF", Some(InterruptFlag)),
(0xd538_4300, "mrs x0,ALLINT", Some(InterruptFlag)),
(
0xd538_f200,
"mrs x0,S3_0_C15_C2_0",
Some(ModelSpecificRegister),
),
(
0xd53f_b000,
"mrs x0,S3_7_C11_C0_0",
Some(ModelSpecificRegister),
),
(0xd53b_f000, "mrs x0,S3_3_C15_C0_0", None),
(0xd53b_e04d, "mrs x13,CNTVCT_EL0", None),
(0xd53b_4208, "mrs x8,NZCV", None),
(0xd503_41df, "msr daifset,#1", Some(InterruptFlag)),
(0xd503_42ff, "msr daifclr,#2", Some(InterruptFlag)),
(0xd501_411f, "msr allint,#1", Some(InterruptFlag)),
(0xd500_419f, "msr pan,#1", Some(ControlRegister)),
(0xd500_40bf, "msr spsel,#0", Some(ControlRegister)),
(0xd508_871f, "tlbi VMALLE1", Some(CacheOrTlb)),
(0xd508_931f, "tlbi VMALLE1ISnXS", Some(CacheOrTlb)),
(0xd50b_7e20, "dc CIVAC,x0", Some(CacheOrTlb)),
(0xd50b_7420, "dc ZVA,x0", Some(CacheOrTlb)),
(0xd50b_7f01, "dc CIVAOC,x1", Some(CacheOrTlb)),
(0xd508_751f, "ic IALLU", Some(CacheOrTlb)),
(0xd508_7855, "at S1E0R,x21", Some(Other)),
(0xd508_7901, "at S1E1RP,x1", Some(Other)),
(0xd50b_7388, "sys #3,C7,C3,#4,x8", Some(Other)),
(0xd508_7f00, "sys #0,C7,C15,#0,x0", Some(Other)),
(0xd508_9000, "sys #0,C9,C0,#0,x0", Some(Other)),
(0xd52e_954d, "sysl x13,#6,C9,C5,#2", Some(Other)),
(0xd528_8701, "sysl x1,#0,C8,C7,#0", Some(Other)),
(0xd400_0002, "hvc #0", Some(Virtualization)),
(0xd400_0003, "smc #0", Some(Virtualization)),
(0xd400_0001, "svc #0", None),
(0xd69f_03e0, "eret", Some(Other)),
(0xd4a0_0001, "dcps1", Some(Other)),
(0xd503_201f, "nop", None),
] {
let one = shapes(word);
assert_eq!(one.privilege, family, "`{text}` {word:#010x}: {one:?}");
assert_eq!(one.privileged, family.is_some(), "`{text}`: {one:?}");
}
assert_eq!(shapes(0xd508_931f).mnemonic, "tlbi");
assert_eq!(shapes(0xd508_7901).mnemonic, "at");
assert_eq!(shapes(0xd50b_7f01).mnemonic, "dc");
assert_eq!(shapes(0xd508_7f00).mnemonic, "sys");
assert_eq!(shapes(0xd508_9000).mnemonic, "sys");
assert_eq!(shapes(0xd52e_954d).mnemonic, "sysl");
assert_eq!(shapes(0xd528_8701).mnemonic, "sysl");
}
#[test]
fn test_only_allocated_maintenance_encodings_are_named() {
let mut named: std::collections::BTreeMap<String, (u32, u64)> = Default::default();
for crn in 0b0111..=0b1001_u32 {
for crm in 0..16_u32 {
for op1 in 0..8_u32 {
for op2 in 0..8_u32 {
let word =
0xd508_001f | (op1 << 16) | (crn << 12) | (crm << 8) | (op2 << 5);
let entry = named.entry(shapes(word).mnemonic).or_default();
entry.0 += 1;
entry.1 += u64::from(word);
}
}
}
}
assert_eq!(
named,
std::collections::BTreeMap::from([
("at".to_string(), (17, 60_762_751_919)),
("dc".to_string(), (40, 142_969_748_920)),
("ic".to_string(), (3, 10_722_499_453)),
("sys".to_string(), (2_842, 10_158_271_623_430)),
("tlbi".to_string(), (170, 607_632_041_494)),
])
);
}
#[test]
fn test_a_pointer_authentication_hint_writes_the_link_register() {
let sign = shapes(0xd503_237f);
assert_eq!(sign.mnemonic, "pacibsp");
assert!(sign.operands.is_empty(), "{sign:?}");
assert_eq!(spellings(&sign.writes), ["lr"]);
assert_eq!(spellings(&sign.reads), ["lr", "sp"]);
assert_eq!(shapes(0xd503_23ff).mnemonic, "autibsp");
let strip = shapes(0xd503_20ff);
assert_eq!(strip.mnemonic, "xpaclri");
assert_eq!(spellings(&strip.reads), ["lr"]);
let scratch = shapes(0xd503_211f);
assert_eq!(scratch.mnemonic, "pacia1716");
assert_eq!(spellings(&scratch.writes), ["xip1"]);
assert_eq!(spellings(&scratch.reads), ["xip1", "xip0"]);
assert_eq!(shapes(0xd503_201f).mnemonic, "nop");
assert!(shapes(0xd503_203f).writes.is_empty());
}
#[test]
fn test_a_barrier_names_its_domain() {
let barrier = shapes(0xd503_3abf);
assert_eq!(barrier.mnemonic, "dmb");
assert_eq!(barrier.operands, [Operand::Other("ishst".to_string())]);
assert!(!barrier.privileged, "a barrier needs no privilege");
assert_eq!(shapes(0xd503_3f9f).mnemonic, "dsb");
assert_eq!(shapes(0xd503_3fdf).mnemonic, "isb");
}
#[test]
fn test_the_vector_boundary_is_decoded_and_the_rest_names_itself() {
let from_float = shapes(0x1e26_0043);
assert_eq!(from_float.mnemonic, "fmov");
assert_eq!(spellings(&from_float.writes), ["x3"]);
assert_eq!(spellings(&from_float.reads), ["s2"]);
let extract = shapes(0x4e18_3e28);
assert_eq!(extract.mnemonic, "umov");
assert_eq!(spellings(&extract.writes), ["x8"]);
assert_eq!(extract.operands[1], Operand::Other("v17.d[1]".to_string()));
let insert = shapes(0x4e08_1d10);
assert_eq!(insert.mnemonic, "ins");
assert_eq!(spellings(&insert.reads), ["x8", "v16"]);
assert_eq!(spellings(&insert.writes), ["v16"]);
let compare = shapes(0x1e21_2000);
assert_eq!(compare.mnemonic, "fcmp");
assert!(compare.writes_flags);
let vector = shapes(0x6f00_e402);
assert_eq!(
vector.operands,
[Operand::Undecoded("advanced-simd".to_string())]
);
assert!(
vector.mnemonic.is_empty(),
"the rendering's token is better"
);
assert!(vector.writes.is_empty());
assert_eq!(
shapes(0xa5e0_a01f).operands,
[Operand::Undecoded("sve".to_string())]
);
assert_eq!(
shapes(0x8000_0005).operands,
[Operand::Undecoded("reserved".to_string())]
);
}
#[test]
fn test_a_write_reaches_the_whole_register_and_the_zero_register_holds_nothing() {
let wide = shapes(0xd280_0016);
let narrow = shapes(0x5280_0016);
assert_eq!(spellings(&wide.writes), ["x22"]);
assert_eq!(spellings(&narrow.writes), ["x22"]);
assert_eq!(narrow.writes[0].width, 8);
assert_eq!(
narrow.operands[0],
register("w22", "x22", 4),
"the operand keeps the spelling it was written with"
);
let masked = shapes(0xea1f_018c);
assert_eq!(masked.operands[2], register("xzr", "xzr", 8));
assert_eq!(spellings(&masked.reads), ["x12"]);
let store = shapes(0x7900_5fff);
assert_eq!(spellings(&store.reads), ["sp"]);
assert!(store.writes.is_empty());
}
#[test]
fn test_the_register_spellings_are_the_engine_s() {
let scratch = shapes(0xf945_2610);
assert_eq!(scratch.operands[0], register("xip0", "xip0", 8));
assert_eq!(shapes(0xd518_d092).operands[1], register("xpr", "xpr", 8));
let narrow = shapes(0x3170_6e50);
assert_eq!(narrow.operands[0], register("wip0", "xip0", 4));
assert_eq!(narrow.operands[1], register("wpr", "xpr", 4));
let frame = shapes(0x9100_03fd);
assert_eq!(frame.mnemonic, "mov");
assert_eq!(frame.effect, Effect::Move);
assert_eq!(frame.operands[0], register("fp", "fp", 8));
assert_eq!(frame.operands[1], register("sp", "sp", 8));
}
#[test]
fn test_a_test_and_branch_takes_its_width_from_the_bit_number() {
let low = decode(0x3680_0208, 0xfffff802_e9ef919c);
assert_eq!(low.mnemonic, "tbz");
assert_eq!(low.operands[0], register("w8", "x8", 4));
assert_eq!(low.operands[1], Operand::Immediate(16));
assert_eq!(low.flow, Flow::Branch(Some(0xfffff802_e9ef91dc)));
let high = shapes(0xb6f8_0148);
assert_eq!(high.operands[0], register("x8", "x8", 8));
assert_eq!(high.operands[1], Operand::Immediate(0x3f));
let compare = decode(0xb400_0293, 0xfffff802_ea3773f0);
assert_eq!(compare.mnemonic, "cbz");
assert_eq!(compare.operands[0], register("x19", "x19", 8));
assert_eq!(spellings(&compare.reads), ["x19"]);
assert_eq!(
decode(0x3500_0128, 0xfffff802_ea69dc80).operands[0],
register("w8", "x8", 4)
);
}
#[test]
fn test_an_unallocated_encoding_names_itself() {
assert_eq!(
shapes(0x1200_fc00).operands,
[Operand::Undecoded("unallocated".to_string())]
);
assert_eq!(
shapes(0x3280_0000).operands,
[Operand::Undecoded("unallocated".to_string())]
);
assert_eq!(
shapes(0x52c0_0000).operands,
[Operand::Undecoded("unallocated".to_string())]
);
}
#[test]
fn test_an_authenticated_branch_spells_its_key_after_the_a() {
let a_key = shapes(0xd71f_0843);
assert_eq!(a_key.mnemonic, "braa");
assert_eq!(spellings(&a_key.reads), ["x2", "x3"]);
let b_key = shapes(0xd71f_0c43);
assert_eq!(b_key.mnemonic, "brab", "not `brba`: {b_key:?}");
assert_eq!(b_key.flow, Flow::Jmp(None));
assert_eq!(shapes(0xd61f_085f).mnemonic, "braaz");
assert_eq!(shapes(0xd61f_0c5f).mnemonic, "brabz");
let call = shapes(0xd73f_0c43);
assert_eq!(call.mnemonic, "blrab");
assert_eq!(spellings(&call.writes), ["lr"]);
assert_eq!(shapes(0xd63f_085f).mnemonic, "blraaz");
assert_eq!(shapes(0xd65f_0bff).mnemonic, "retaa");
assert_eq!(shapes(0xd65f_0fff).mnemonic, "retab");
assert_eq!(shapes(0xd69f_0fff).mnemonic, "eretab");
assert!(shapes(0xd69f_0fff).privileged);
assert_eq!(spellings(&shapes(0xd69f_0bff).reads), ["sp"]);
assert!(shapes(0xd69f_03e0).reads.is_empty());
}
#[test]
fn test_the_flag_manipulation_fields_are_not_privileged() {
for (word, mnemonic) in [
(0xd500_401f_u32, "cfinv"),
(0xd500_403f, "xaflag"),
(0xd500_405f, "axflag"),
] {
let flags = shapes(word);
assert_eq!(flags.mnemonic, mnemonic);
assert!(
flags.operands.is_empty(),
"{mnemonic} takes none: {flags:?}"
);
assert!(!flags.privileged, "{word:#010x} is EL0: {flags:?}");
assert!(flags.writes_flags, "{word:#010x}: {flags:?}");
}
assert_eq!(
shapes(0xd500_4c1f).operands,
[Operand::Undecoded("unallocated".to_string())]
);
for (word, field) in [
(0xd500_407f_u32, "uao"),
(0xd500_409f, "pan"),
(0xd500_40bf, "spsel"),
(0xd503_41df, "daifset"),
(0xd503_42ff, "daifclr"),
] {
let one = shapes(word);
assert_eq!(one.operands[0], Operand::Other(field.to_string()));
assert!(one.privileged, "{field} is EL1: {one:?}");
}
assert!(!shapes(0xd503_405f).privileged);
}
#[test]
fn test_a_vector_structure_access_keeps_its_post_index_register_out_of_the_address() {
let replicate = shapes(0x4dca_efb2);
assert_eq!(replicate.mnemonic, "ld3r");
let Operand::Memory(memory) = &replicate.operands[1] else {
panic!("{replicate:?}");
};
assert_eq!(memory.index, None, "the access is at the base alone");
assert_eq!(memory.base.as_deref(), Some("fp"));
assert_eq!(replicate.operands[2], register("x10", "x10", 8));
assert_eq!(spellings(&replicate.writes), ["v18", "v19", "v20", "fp"]);
assert_eq!(spellings(&replicate.reads), ["fp", "x10"]);
let lanes = shapes(0x0dff_0020);
assert_eq!(lanes.mnemonic, "ld2");
assert_eq!(spellings(&lanes.writes), ["v0", "v1", "x1"]);
assert_eq!(spellings(&lanes.reads), ["v0", "v1", "x1"]);
let whole = shapes(0x4d40_c110);
assert_eq!(whole.mnemonic, "ld1r");
assert_eq!(spellings(&whole.writes), ["v16"]);
assert_eq!(spellings(&whole.reads), ["x8"]);
for word in [0x0d01_8774_u32, 0x0d41_8774] {
assert_eq!(
shapes(word).operands,
[Operand::Undecoded("unallocated".to_string())],
"{word:#010x}"
);
}
assert_eq!(shapes(0x0d00_2140).mnemonic, "st3");
let amount = |word: u32| match shapes(word).operands.last() {
Some(Operand::Other(text)) => text.clone(),
other => panic!("{word:#010x}: {other:?}"),
};
assert_eq!(amount(0x0dff_0020), "#0x2", "ld2 of one byte each");
assert_eq!(
amount(0x0cdf_7020),
"#0x8",
"ld1 of one eight-byte register"
);
assert_eq!(
amount(0x4cdf_2020),
"#0x40",
"ld1 of four sixteen-byte registers"
);
assert_eq!(
amount(0x0d9f_8020),
"#0x4",
"st1 of one single-precision lane"
);
}
#[test]
fn test_a_vector_structure_access_reports_how_much_it_transfers() {
let transferred = |word: u32| {
let shaped = shapes(word);
let memory = shaped.operands.iter().find_map(|operand| match operand {
Operand::Memory(memory) => Some(memory.clone()),
_ => None,
});
memory
.unwrap_or_else(|| panic!("{word:#010x}: {shaped:?}"))
.size
};
assert_eq!(transferred(0x4c40_2020), Some(64));
assert_eq!(transferred(0x0c40_7020), Some(8));
assert_eq!(transferred(0x4c40_a020), Some(32));
assert_eq!(transferred(0x0dff_0020), Some(2));
assert_eq!(transferred(0x4d20_7972), Some(8));
assert_eq!(transferred(0x4d40_c110), Some(1));
assert_eq!(spellings(&shapes(0x4d40_c110).writes), ["v16"]);
assert_eq!(transferred(0x4dca_efb2), Some(24));
}
#[test]
fn test_an_access_reports_its_width_unless_it_genuinely_has_none() {
let width = |word: u32| {
let shaped = shapes(word);
let sizes: Vec<_> = shaped
.operands
.iter()
.filter_map(|operand| match operand {
Operand::Memory(memory) => Some(memory.size),
_ => None,
})
.collect();
assert!(!sizes.is_empty(), "{word:#010x}: {shaped:?}");
sizes
};
assert_eq!(width(0xf000_187e), [None]);
assert_eq!(shapes(0xf000_187e).effect, Effect::LoadAddress);
assert_eq!(width(0xf980_0021), [None]);
assert_eq!(width(0x1941_0440), [None, None]);
assert_eq!(width(0xd9a0_0000), [None]);
assert_eq!(width(0xd960_0020), [Some(16)]);
assert_eq!(shapes(0xd960_0020).mnemonic, "ldg");
assert_eq!(width(0xd9e0_0420), [Some(32)]);
assert_eq!(shapes(0xd9e0_0420).mnemonic, "stz2g");
assert_eq!(width(0xa942_7bfd), [Some(16)]);
assert_eq!(width(0x4c40_a020), [Some(32)]);
assert_eq!(width(0xf940_0021), [Some(8)]);
}
#[test]
fn test_a_barrier_option_is_read_from_its_own_field() {
let sync = shapes(0xd503_3fdf);
assert_eq!(sync.mnemonic, "isb");
assert_eq!(sync.operands, [Operand::Other("sy".to_string())]);
let numbered = shapes(0xd503_37df);
assert_eq!(numbered.mnemonic, "isb");
assert_eq!(numbered.operands, [Operand::Immediate(7)]);
for (word, mnemonic) in [(0xd503_309f_u32, "ssbb"), (0xd503_349f, "pssbb")] {
let barrier = shapes(word);
assert_eq!(barrier.mnemonic, mnemonic, "{word:#010x}");
assert!(barrier.operands.is_empty(), "{barrier:?}");
}
assert_eq!(shapes(0xd503_3f9f).mnemonic, "dsb");
assert_eq!(
shapes(0xd503_3f9f).operands,
[Operand::Other("sy".to_string())]
);
assert_eq!(
shapes(0xd503_3bbf).operands,
[Operand::Other("ish".to_string())]
);
}
#[test]
fn test_a_compare_and_swap_pair_refuses_an_odd_register() {
let pair = shapes(0x482a_7c0c);
assert_eq!(pair.mnemonic, "casp");
assert_eq!(spellings(&pair.writes), ["x10", "x11"]);
assert_eq!(spellings(&pair.reads), ["x10", "x11", "x12", "x13", "x0"]);
for word in [0x482b_7c0c_u32, 0x482a_7c0d] {
assert_eq!(
shapes(word).operands,
[Operand::Undecoded("unallocated".to_string())],
"{word:#010x}"
);
}
}
#[test]
fn test_a_branch_form_fixes_the_fields_it_does_not_use() {
let unallocated = |word: u32| {
assert_eq!(
shapes(word).operands,
[Operand::Undecoded("unallocated".to_string())],
"{word:#010x}"
);
};
assert_eq!(shapes(0xd61f_0000).mnemonic, "br");
unallocated(0xd61f_0001);
assert_eq!(shapes(0xd61f_085f).mnemonic, "braaz");
unallocated(0xd61f_0840);
assert_eq!(shapes(0xd65f_0bff).mnemonic, "retaa");
unallocated(0xd65f_0801);
assert_eq!(shapes(0xd69f_03e0).mnemonic, "eret");
unallocated(0xd69f_0000);
assert_eq!(shapes(0xd6bf_03e0).mnemonic, "drps");
unallocated(0xd6bf_0be0);
let modifier = shapes(0xd71f_0843);
assert_eq!(modifier.mnemonic, "braa");
assert_eq!(spellings(&modifier.reads), ["x2", "x3"]);
unallocated(0xd71f_0000);
unallocated(0xd73f_0000);
}
#[test]
fn test_an_addressing_mode_constrains_which_transfers_it_allows() {
let unallocated = |word: u32| {
assert_eq!(
shapes(word).operands,
[Operand::Undecoded("unallocated".to_string())],
"{word:#010x}"
);
};
assert_eq!(shapes(0xf880_0000).mnemonic, "prfum");
unallocated(0xf880_0400);
unallocated(0xf880_0800);
unallocated(0xf880_0c00);
unallocated(0x3c00_0800);
unallocated(0x3c40_0800);
assert_eq!(shapes(0x3c00_0000).mnemonic, "stur");
assert_eq!(shapes(0xf800_0800).mnemonic, "sttr");
assert_eq!(shapes(0xf840_0800).mnemonic, "ldtr");
assert_eq!(shapes(0xb880_0800).mnemonic, "ldtrsw");
for word in [0xd960_0020_u32, 0xd960_1020, 0xd960_f020] {
assert_eq!(shapes(word).mnemonic, "ldg", "{word:#010x}");
}
let Operand::Memory(memory) = &shapes(0xd960_f020).operands[1] else {
panic!("{:?}", shapes(0xd960_f020));
};
assert_eq!(memory.displacement, 240);
assert_eq!(shapes(0xd9a0_0020).mnemonic, "stgm");
unallocated(0xd9a0_1020);
}
#[test]
fn test_the_reserved_spaces_one_allocated_member_is_decoded() {
let trap = shapes(0x0000_1234);
assert_eq!(trap.mnemonic, "udf");
assert_eq!(trap.operands, [Operand::Immediate(0x1234)]);
assert_eq!(trap.flow, Flow::Trap);
let padding = shapes(0x0000_0000);
assert_eq!(padding.mnemonic, "udf");
assert_eq!(padding.operands, [Operand::Immediate(0)]);
let reserved = shapes(0x8000_0005);
assert_eq!(reserved.mnemonic, "");
assert_eq!(
reserved.operands,
[Operand::Undecoded("reserved".to_string())]
);
assert_eq!(reserved.flow, Flow::Trap);
}
#[test]
fn test_the_upper_lane_moves_and_the_javascript_conversion_reach_what_they_name() {
let out_of = shapes(0x9eae_0020);
assert_eq!(out_of.mnemonic, "fmov");
assert_eq!(spellings(&out_of.writes), ["x0"]);
assert_eq!(spellings(&out_of.reads), ["v1"]);
let into = shapes(0x9eaf_0062);
assert_eq!(spellings(&into.writes), ["v2"]);
assert_eq!(spellings(&into.reads), ["x3", "v2"]);
let javascript = shapes(0x1e7e_0000);
assert_eq!(javascript.mnemonic, "fjcvtzs");
assert!(javascript.writes_flags, "{javascript:?}");
assert_eq!(spellings(&javascript.writes), ["x0"]);
assert!(!shapes(0x1e18_0000).writes_flags, "fcvtzs");
}
#[test]
fn test_the_tagging_and_acquiring_accesses_are_shaped() {
let tag = shapes(0xd960_0128);
assert_eq!(tag.mnemonic, "ldg");
assert_eq!(spellings(&tag.writes), ["x8"]);
assert_eq!(
spellings(&tag.reads),
["x8", "x9"],
"the address survives: {tag:?}"
);
let store = shapes(0xd920_1462);
assert_eq!(store.mnemonic, "stg");
assert_eq!(store.operands[2], Operand::Other("#0x10".to_string()));
assert_eq!(spellings(&store.writes), ["x3"]);
assert_eq!(spellings(&store.reads), ["x2", "x3"]);
let Operand::Memory(memory) = &store.operands[1] else {
panic!("{store:?}");
};
assert_eq!(memory.displacement, 0);
assert_eq!(shapes(0xd9a0_0128).mnemonic, "stgm");
let acquire = shapes(0x199b_7088);
assert_eq!(acquire.mnemonic, "ldapursb");
assert_eq!(acquire.effect, Effect::MoveSigned);
let Operand::Memory(memory) = &acquire.operands[1] else {
panic!("{acquire:?}");
};
assert_eq!(memory.displacement, -0x49);
assert_eq!(shapes(0xd940_4020).mnemonic, "ldapur");
assert_eq!(shapes(0xd900_4020).mnemonic, "stlur");
assert_eq!(
shapes(0xd980_4020).operands,
[Operand::Undecoded("unallocated".to_string())]
);
}
#[test]
fn test_the_flag_setting_pointer_subtraction_is_not_rejected_with_its_class() {
let subps = shapes(0xbac5_0083);
assert_eq!(subps.mnemonic, "subps");
assert!(subps.writes_flags, "{subps:?}");
assert_eq!(spellings(&subps.writes), ["x3"]);
assert_eq!(spellings(&subps.reads), ["x4", "x5"]);
let subp = shapes(0x9ac5_0083);
assert_eq!(subp.mnemonic, "subp");
assert!(!subp.writes_flags);
assert_eq!(
shapes(0xbac5_0883).operands,
[Operand::Undecoded("unallocated".to_string())]
);
assert_eq!(
shapes(0x3ac5_0083).operands,
[Operand::Undecoded("unallocated".to_string())]
);
}
#[test]
fn test_an_unread_instruction_is_a_different_shape_from_an_unshaped_operand() {
let unread = shapes(0x6f00_e402);
assert_eq!(
unread.operands,
[Operand::Undecoded("advanced-simd".to_string())]
);
assert!(unread.writes.is_empty() && unread.reads.is_empty());
assert!(!unread.privileged && !unread.writes_flags);
assert_eq!(unread.effect, Effect::Other);
let shaped = shapes(0xd503_3abf);
assert_eq!(shaped.mnemonic, "dmb");
assert_eq!(shaped.operands, [Operand::Other("ishst".to_string())]);
assert!(
!shaped
.operands
.iter()
.any(|operand| matches!(operand, Operand::Undecoded(_))),
"a named operand kind is not an unread instruction: {shaped:?}"
);
}
#[test]
fn test_the_pointer_arithmetic_reads_register_thirty_one_as_the_stack_pointer() {
let tag = shapes(0x9adf_13ff);
assert_eq!(tag.mnemonic, "irg");
assert_eq!(spellings(&tag.writes), ["sp"], "{tag:?}");
assert_eq!(spellings(&tag.reads), ["sp"], "{tag:?}");
for (word, flags) in [(0x9ac2_03e0_u32, false), (0xbac2_03e0, true)] {
let one = shapes(word);
assert_eq!(spellings(&one.writes), ["x0"], "{one:?}");
assert_eq!(spellings(&one.reads), ["sp", "x2"], "{one:?}");
assert_eq!(one.writes_flags, flags, "{one:?}");
}
let ordinary = shapes(0x9adf_1000);
assert_eq!(spellings(&ordinary.writes), ["x0"]);
assert_eq!(spellings(&ordinary.reads), ["x0"]);
let divide = shapes(0x9adf_0800);
assert_eq!(divide.mnemonic, "udiv");
assert_eq!(spellings(÷.reads), ["x0"], "{divide:?}");
let frame = shapes(0xd920_0bff);
assert_eq!(frame.mnemonic, "stg");
assert_eq!(spellings(&frame.reads), ["sp", "sp"], "{frame:?}");
assert_eq!(spellings(&shapes(0xd960_03ff).reads), ["sp"]);
}
#[test]
fn test_a_two_granule_tag_store_reports_both_granules() {
let size = |word: u32| {
let one = shapes(word);
let Operand::Memory(memory) = &one.operands[1] else {
panic!("{one:?}");
};
(one.mnemonic.clone(), memory.size)
};
assert_eq!(size(0xd9e0_0862), ("stz2g".to_string(), Some(32)));
assert_eq!(size(0xd920_0862), ("stg".to_string(), Some(16)));
assert_eq!(size(0xd9a0_0128), ("stgm".to_string(), None));
}
#[test]
fn test_a_cross_register_file_move_is_a_move_and_a_conversion_is_not() {
for word in [0x9e66_0020_u32, 0x1e26_0043, 0x9e67_0020] {
let one = shapes(word);
assert_eq!(one.mnemonic, "fmov");
assert_eq!(one.effect, Effect::Move, "{word:#010x}: {one:?}");
}
let convert = shapes(0x1e38_0000);
assert_eq!(convert.mnemonic, "fcvtzs");
assert_eq!(convert.effect, Effect::Other);
assert_eq!(shapes(0x9eae_0020).effect, Effect::Move);
assert_eq!(
shapes(0x9eaf_0062).effect,
Effect::Other,
"what it holds afterwards is the source beside the lane it kept"
);
}
#[test]
fn test_register_thirty_one_is_the_stack_pointer_in_exactly_these_positions() {
let touched = |word: u32| {
let one = shapes(word);
let mut names: Vec<String> = one
.reads
.iter()
.chain(one.writes.iter())
.map(|register| register.name.clone())
.collect();
names.sort();
names.dedup();
(one.mnemonic.clone(), names)
};
for (word, what) in [
(0xf940_03ff_u32, "ldr xzr,[sp] — any addressing mode's base"),
(0x9100_03ff, "mov sp,sp — add immediate, both ends"),
(
0xb100_03ff,
"cmn sp,#0 — add immediate's source when it sets flags",
),
(0x8b3f_63ff, "add sp,sp,xzr — the extended-register form"),
(
0x9240_03ff,
"and sp,xzr,#1 — a logical immediate's destination",
),
(
0xb266_97ff,
"mov sp,#-0x4000000 — and the same through its `mov` alias",
),
(0x9180_03ff, "addg sp,sp,#0,#0"),
(0x9adf_13ff, "irg sp,sp"),
(0x9ac1_17e0, "gmi x0,sp,x1"),
(0x9ac2_03e0, "subp x0,sp,x2 — the left source"),
(0x9adf_0020, "subp x0,x1,sp — and the right one"),
(0x9adf_3020, "pacga x0,x1,sp — the modifier"),
(0xdac1_03e0, "pacia x0,sp — the modifier again"),
(0xd71f_083f, "braa x1,sp — and once more"),
(0xd920_0bff, "stg sp,[sp] — a tag store's transfer register"),
(0xd960_03ff, "ldg xzr,[sp] — but not a tag load's"),
] {
let (mnemonic, names) = touched(word);
assert!(
names.iter().any(|name| name == "sp"),
"{word:#010x} `{what}`: {mnemonic} touched {names:?}"
);
}
for (word, what) in [
(
0xeb1f_03ff_u32,
"cmp xzr,xzr — the shifted-register arithmetic",
),
(
0xf240_03ff,
"tst xzr,#1 — `ands`, which is why the alias exists",
),
(0xaa1f_03ff, "mov xzr,xzr — a logical shifted register"),
] {
let (mnemonic, names) = touched(word);
assert!(
names.is_empty(),
"{word:#010x} `{what}`: {mnemonic} touched {names:?}"
);
}
}
#[test]
fn test_a_consistency_hinted_branch_keeps_its_own_mnemonic() {
let ordinary = decode(0x5400_0000, 0x1000);
assert_eq!(ordinary.mnemonic, "b.eq");
let hinted = decode(0x5400_0010, 0x1000);
assert_eq!(hinted.mnemonic, "bc.eq");
assert_eq!(hinted.condition, ordinary.condition);
assert_eq!(hinted.flow, ordinary.flow);
assert_eq!(hinted.operands, ordinary.operands);
}
#[test]
fn test_an_authenticated_load_is_constrained_by_its_size_and_nothing_else() {
let negative = shapes(0xf86c_24a2);
assert_eq!(negative.mnemonic, "ldraa");
assert_eq!(spellings(&negative.writes), ["x2"]);
let Operand::Memory(memory) = &negative.operands[1] else {
panic!("{negative:?}");
};
assert_eq!(memory.displacement, -318 * 8);
let positive = shapes(0xf82c_24a2);
assert_eq!(positive.mnemonic, "ldraa");
let key = shapes(0xf8fc_fcf6);
assert_eq!(key.mnemonic, "ldrab");
assert_eq!(spellings(&key.writes), ["x22", "x7"]);
assert_eq!(spellings(&key.reads), ["x7"]);
assert_eq!(spellings(&shapes(0xf82b_9fff).writes), ["sp"]);
}
#[test]
fn test_a_memory_copy_advances_all_three_of_its_registers() {
let prologue = shapes(0x1901_0440);
assert_eq!(prologue.mnemonic, "cpyfp");
assert_eq!(spellings(&prologue.reads), ["x0", "x1", "x2"]);
assert_eq!(spellings(&prologue.writes), ["x0", "x1", "x2"]);
let Operand::Memory(destination) = &prologue.operands[0] else {
panic!("{prologue:?}");
};
assert_eq!(destination.base.as_deref(), Some("x0"));
assert_eq!(destination.size, None);
assert!(matches!(prologue.operands[1], Operand::Memory(_)));
assert_eq!(prologue.operands[2], register("x2", "x2", 8));
assert_eq!(shapes(0x1941_0440).mnemonic, "cpyfm");
assert_eq!(shapes(0x1981_0440).mnemonic, "cpyfe");
assert_eq!(shapes(0x1900_5440).mnemonic, "cpyfpwtwn");
assert_eq!(shapes(0x1900_f400).mnemonic, "cpyfptn");
assert_eq!(shapes(0x1d01_0440).mnemonic, "cpyp");
let set = shapes(0x19c2_0420);
assert_eq!(set.mnemonic, "setp");
assert!(matches!(set.operands[0], Operand::Memory(_)));
assert_eq!(set.operands[2], register("x2", "x2", 8));
assert_eq!(spellings(&set.writes), ["x0", "x1"]);
assert_eq!(spellings(&set.reads), ["x0", "x1", "x2"]);
assert_eq!(shapes(0x19c0_b400).mnemonic, "setetn");
assert_eq!(shapes(0x1dc0_0400).mnemonic, "setgp");
assert_eq!(
shapes(0x19c0_c400).operands,
[Operand::Undecoded("unallocated".to_string())]
);
assert!(shapes(0x1901_0440).writes_flags, "cpyfp");
assert!(shapes(0x19c2_0420).writes_flags, "setp");
assert!(!shapes(0x1941_0440).writes_flags, "cpyfm");
assert!(!shapes(0x1981_0440).writes_flags, "cpyfe");
assert!(!shapes(0x19c2_4420).writes_flags, "setm");
}
#[test]
fn test_the_literal_minimum_and_maximum_share_the_tagged_add_s_slot() {
for (word, mnemonic) in [
(0x11c0_0000_u32, "smax"),
(0x11c4_0000, "umax"),
(0x11c8_0000, "smin"),
(0x11cc_0000, "umin"),
] {
let one = shapes(word);
assert_eq!(one.mnemonic, mnemonic);
assert_eq!(spellings(&one.writes), ["x0"]);
}
assert_eq!(shapes(0x11c3_fc00).operands[2], Operand::Immediate(!0));
assert_eq!(shapes(0x11c7_fc00).operands[2], Operand::Immediate(0xff));
let tagged = shapes(0x9180_03ff);
assert_eq!(tagged.mnemonic, "addg");
assert_eq!(spellings(&tagged.writes), ["sp"]);
}
#[test]
fn test_a_lane_extraction_is_a_move_and_says_which_kind() {
let zero = shapes(0x0e05_3c20);
assert_eq!(zero.mnemonic, "umov");
assert_eq!(zero.effect, Effect::Move);
assert_eq!(spellings(&zero.writes), ["x0"]);
let signed = shapes(0x0e0e_2ce6);
assert_eq!(signed.mnemonic, "smov");
assert_eq!(signed.effect, Effect::MoveSigned);
assert_eq!(shapes(0x4e08_1d10).effect, Effect::Other);
assert_eq!(shapes(0x4e04_0c60).effect, Effect::Other);
}
#[test]
fn test_a_prefetch_claims_no_transfer_width() {
let size = |word: u32| {
let one = shapes(word);
let Operand::Memory(memory) = &one.operands[1] else {
panic!("{one:?}");
};
(one.mnemonic.clone(), memory.size, memory.displacement)
};
assert_eq!(size(0xf980_0130), ("prfm".to_string(), None, 0));
assert_eq!(size(0xf980_1021), ("prfm".to_string(), None, 0x20));
assert_eq!(size(0xf880_c040), ("prfum".to_string(), None, 0xc));
assert_eq!(size(0xd807_aa98).1, None);
assert_eq!(size(0xf940_0021), ("ldr".to_string(), Some(8), 0));
}
#[test]
fn test_the_flow_is_the_one_the_flow_decoder_gives() {
for word in [
0x97ff_fcc4_u32,
0xd63f_01e0,
0xd65f_03c0,
0xd43e_0000,
0x5400_02c1,
0x0000_0000,
0x6f00_e402,
] {
assert_eq!(
decode(word, ANYWHERE).flow,
super::super::flow(word, ANYWHERE),
"{word:#010x}"
);
}
}
}