pub type GprNum = u8;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MovWideKind {
Movn,
Movz,
Movk,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LdrRegOp {
pub rt: GprNum,
pub rn: GprNum,
pub rm: GprNum,
pub shift: u8,
pub size_bytes: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LdrUimmOp {
pub rt: GprNum,
pub rn: GprNum,
pub offset: u64,
pub size_bytes: u8,
pub is_store: bool,
}
#[inline]
fn bits(insn: u32, hi: u8, lo: u8) -> u32 {
debug_assert!(hi < 32 && lo <= hi);
let width = hi - lo + 1;
(insn >> lo) & ((1u32 << width) - 1)
}
#[inline]
fn sign_extend(value: u64, bits: u8) -> i64 {
debug_assert!(bits > 0 && bits <= 64);
let shift = 64 - bits;
((value << shift) as i64) >> shift
}
#[must_use]
pub fn decode_adrp(insn: u32, pc: u64) -> Option<(GprNum, u64)> {
if (insn & 0x9F00_0000) != 0x9000_0000 {
return None;
}
let immlo = bits(insn, 30, 29) as u64;
let immhi = bits(insn, 23, 5) as u64;
let imm21 = (immhi << 2) | immlo;
let offset = sign_extend(imm21, 21) << 12;
let page_base = pc & !0xFFFu64;
let target = page_base.wrapping_add(offset as u64);
let rd = bits(insn, 4, 0) as GprNum;
Some((rd, target))
}
#[must_use]
pub fn decode_add_sub_imm(insn: u32) -> Option<(GprNum, GprNum, u64, bool)> {
if (insn & 0x1F00_0000) != 0x1100_0000 {
return None;
}
let sh = bits(insn, 22, 22);
let imm12 = bits(insn, 21, 10) as u64;
let imm = if sh == 1 { imm12 << 12 } else { imm12 };
let rn = bits(insn, 9, 5) as GprNum;
let rd = bits(insn, 4, 0) as GprNum;
let is_sub = bits(insn, 30, 30) == 1;
Some((rd, rn, imm, is_sub))
}
#[must_use]
pub fn decode_mov_wide(insn: u32) -> Option<(GprNum, u64, MovWideKind)> {
if (insn & 0x1F80_0000) != 0x1280_0000 {
return None;
}
let opc = bits(insn, 30, 29);
let kind = match opc {
0b00 => MovWideKind::Movn,
0b10 => MovWideKind::Movz,
0b11 => MovWideKind::Movk,
_ => return None,
};
let hw = bits(insn, 22, 21) as u8;
let imm16 = bits(insn, 20, 5) as u64;
let value = match kind {
MovWideKind::Movz | MovWideKind::Movk => imm16 << (hw as u64 * 16),
MovWideKind::Movn => !(imm16 << (hw as u64 * 16)),
};
let rd = bits(insn, 4, 0) as GprNum;
Some((rd, value, kind))
}
#[must_use]
pub fn decode_mov_reg(insn: u32) -> Option<(GprNum, GprNum)> {
if (insn & 0x7FE0_FC00) != 0x2A00_0000 {
return None;
}
let rn = bits(insn, 9, 5) as GprNum;
if rn != 31 {
return None;
}
let rd = bits(insn, 4, 0) as GprNum;
let rm = bits(insn, 20, 16) as GprNum;
Some((rd, rm))
}
#[must_use]
pub fn decode_ldr_reg(insn: u32) -> Option<LdrRegOp> {
if (insn & 0x3FE0_0C00) != 0x3860_0800 {
return None;
}
let size = bits(insn, 31, 30);
let size_bytes: u8 = 1 << size;
let rt = bits(insn, 4, 0) as GprNum;
let rn = bits(insn, 9, 5) as GprNum;
let rm = bits(insn, 20, 16) as GprNum;
let s = bits(insn, 12, 12);
let shift = if s == 1 { size } else { 0 };
Some(LdrRegOp {
rt,
rn,
rm,
shift: shift as u8,
size_bytes,
})
}
#[must_use]
pub fn decode_ldr_str_uimm(insn: u32) -> Option<LdrUimmOp> {
if (insn & 0x3F00_0000) != 0x3900_0000 {
return None;
}
if bits(insn, 26, 26) != 0 {
return None;
}
let size = bits(insn, 31, 30);
let size_bytes: u8 = 1 << size;
let l = bits(insn, 22, 22);
let imm12 = bits(insn, 21, 10) as u64;
let offset = imm12 * (size_bytes as u64); let rn = bits(insn, 9, 5) as GprNum;
let rt = bits(insn, 4, 0) as GprNum;
Some(LdrUimmOp {
rt,
rn,
offset,
size_bytes,
is_store: l == 0,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AddExtRegOp {
pub rd: GprNum,
pub rn: GprNum,
pub rm: GprNum,
pub option: u8,
pub shift: u8,
pub is_sub: bool,
}
#[must_use]
pub fn decode_add_ext_reg(insn: u32) -> Option<AddExtRegOp> {
if (insn & 0xFF20_0000) != 0x8B20_0000 {
return None;
}
let is_sub = bits(insn, 30, 30) == 1;
let rm = bits(insn, 20, 16) as GprNum;
let option = bits(insn, 15, 13) as u8;
let shift = bits(insn, 12, 10) as u8;
let rn = bits(insn, 9, 5) as GprNum;
let rd = bits(insn, 4, 0) as GprNum;
Some(AddExtRegOp {
rd,
rn,
rm,
option,
shift,
is_sub,
})
}
#[must_use]
pub fn decode_adr(insn: u32, pc: u64) -> Option<(GprNum, u64)> {
if (insn & 0x9F00_0000) != 0x1000_0000 {
return None;
}
let immlo = bits(insn, 30, 29) as u64;
let immhi = bits(insn, 23, 5) as u64;
let imm21 = (immhi << 2) | immlo;
let offset = sign_extend(imm21, 21);
let target = (pc as i64).wrapping_add(offset) as u64;
let rd = bits(insn, 4, 0) as GprNum;
Some((rd, target))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LdrswRegOp {
pub rt: GprNum,
pub rn: GprNum,
pub rm: GprNum,
pub shift: u8,
}
#[must_use]
pub fn decode_ldrsw_reg(insn: u32) -> Option<LdrswRegOp> {
if (insn & 0xFFE0_0C00) != 0xB8A0_0800 {
return None;
}
let rt = bits(insn, 4, 0) as GprNum;
let rn = bits(insn, 9, 5) as GprNum;
let rm = bits(insn, 20, 16) as GprNum;
let s = bits(insn, 12, 12);
let shift = if s == 1 { 2 } else { 0 };
Some(LdrswRegOp { rt, rn, rm, shift })
}
#[must_use]
pub fn decode_branch_reg(insn: u32) -> Option<GprNum> {
if (insn & 0xFE1F_FC00) != 0xD61F_0000 {
return None;
}
Some(bits(insn, 9, 5) as GprNum)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn adrp_decodes_pc_relative_page() {
let (rd, target) = decode_adrp(0xb000_0000, 0x4000).expect("adrp shape");
assert_eq!(rd, 0);
assert_eq!(target, 0x5000);
}
#[test]
fn adrp_decodes_negative_offset() {
let (rd, target) = decode_adrp(0xf0ff_ffe1, 0x8000).expect("adrp shape");
assert_eq!(rd, 1);
assert_eq!(target, 0x7000);
}
#[test]
fn add_imm_unshifted() {
let (rd, rn, imm, is_sub) = decode_add_sub_imm(0x9100_8020).expect("add imm shape");
assert_eq!(rd, 0);
assert_eq!(rn, 1);
assert_eq!(imm, 0x20);
assert!(!is_sub);
}
#[test]
fn mov_wide_movz_with_shift() {
let (rd, val, kind) = decode_mov_wide(0xd2b5_79a0).expect("movz shape");
assert_eq!(rd, 0);
assert_eq!(val, 0xabcd_0000);
assert_eq!(kind, MovWideKind::Movz);
}
#[test]
fn mov_reg_decodes_orr_xzr_alias() {
let (rd, rm) = decode_mov_reg(0xaa01_03e0).expect("mov reg shape");
assert_eq!(rd, 0);
assert_eq!(rm, 1);
}
#[test]
fn ldr_reg_decodes_lsl_3_64bit() {
let op = decode_ldr_reg(0xf862_7820).expect("ldr reg shape");
assert_eq!(op.rt, 0);
assert_eq!(op.rn, 1);
assert_eq!(op.rm, 2);
assert_eq!(op.size_bytes, 8);
assert_eq!(op.shift, 3);
}
#[test]
fn ldr_uimm_decodes_got_load() {
let op = decode_ldr_str_uimm(0xf940_0e30).expect("ldr uimm shape");
assert_eq!(op.rt, 16);
assert_eq!(op.rn, 17);
assert_eq!(op.offset, 0x18);
assert_eq!(op.size_bytes, 8);
assert!(!op.is_store);
}
#[test]
fn ldr_reg_decodes_byte_load() {
let op = decode_ldr_reg(0x3862_6820).expect("ldrb shape");
assert_eq!(op.rt, 0);
assert_eq!(op.rn, 1);
assert_eq!(op.rm, 2);
assert_eq!(op.size_bytes, 1);
assert_eq!(op.shift, 0);
}
#[test]
fn ldr_reg_decodes_halfword_load_lsl1() {
let op = decode_ldr_reg(0x7862_7820).expect("ldrh shape");
assert_eq!(op.rt, 0);
assert_eq!(op.rn, 1);
assert_eq!(op.rm, 2);
assert_eq!(op.size_bytes, 2);
assert_eq!(op.shift, 1);
}
#[test]
fn add_ext_reg_decodes_sxtb_lsl2() {
let op = decode_add_ext_reg(0x8B22_8823).expect("add-ext shape");
assert_eq!(op.rd, 3);
assert_eq!(op.rn, 1);
assert_eq!(op.rm, 2);
assert_eq!(op.option, 0b100);
assert_eq!(op.shift, 2);
assert!(!op.is_sub);
}
#[test]
fn adr_decodes_pc_relative_byte() {
let (rd, target) = decode_adr(0x1000_0020, 0x1000).expect("adr shape");
assert_eq!(rd, 0);
assert_eq!(target, 0x1004);
}
#[test]
fn ldrsw_reg_decodes_lsl_2() {
let op = decode_ldrsw_reg(0xb8a9_7909).expect("ldrsw shape");
assert_eq!(op.rt, 9);
assert_eq!(op.rn, 8);
assert_eq!(op.rm, 9);
assert_eq!(op.shift, 2);
}
#[test]
fn br_decodes_register() {
assert_eq!(decode_branch_reg(0xd61f_0200), Some(16));
}
#[test]
fn blr_decodes_register() {
assert_eq!(decode_branch_reg(0xd63f_0220), Some(17));
}
}