use core::fmt;
use super::isa::{Cond, RegName, ShiftType, bit, field};
#[inline]
#[must_use]
const fn hfield(word: u16, hi: u32, lo: u32) -> u16 {
field(word as u32, hi, lo) as u16
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ImmOp {
Mov,
Cmp,
Add,
Sub,
}
impl ImmOp {
#[must_use]
pub const fn mnemonic(self) -> &'static str {
match self {
ImmOp::Mov => "MOV",
ImmOp::Cmp => "CMP",
ImmOp::Add => "ADD",
ImmOp::Sub => "SUB",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AluOp {
And,
Eor,
Lsl,
Lsr,
Asr,
Adc,
Sbc,
Ror,
Tst,
Neg,
Cmp,
Cmn,
Orr,
Mul,
Bic,
Mvn,
}
impl AluOp {
#[must_use]
pub const fn from_bits(bits: u16) -> AluOp {
match bits & 0xf {
0x0 => AluOp::And,
0x1 => AluOp::Eor,
0x2 => AluOp::Lsl,
0x3 => AluOp::Lsr,
0x4 => AluOp::Asr,
0x5 => AluOp::Adc,
0x6 => AluOp::Sbc,
0x7 => AluOp::Ror,
0x8 => AluOp::Tst,
0x9 => AluOp::Neg,
0xa => AluOp::Cmp,
0xb => AluOp::Cmn,
0xc => AluOp::Orr,
0xd => AluOp::Mul,
0xe => AluOp::Bic,
_ => AluOp::Mvn,
}
}
#[must_use]
pub const fn mnemonic(self) -> &'static str {
match self {
AluOp::And => "AND",
AluOp::Eor => "EOR",
AluOp::Lsl => "LSL",
AluOp::Lsr => "LSR",
AluOp::Asr => "ASR",
AluOp::Adc => "ADC",
AluOp::Sbc => "SBC",
AluOp::Ror => "ROR",
AluOp::Tst => "TST",
AluOp::Neg => "NEG",
AluOp::Cmp => "CMP",
AluOp::Cmn => "CMN",
AluOp::Orr => "ORR",
AluOp::Mul => "MUL",
AluOp::Bic => "BIC",
AluOp::Mvn => "MVN",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HiOp {
Add,
Cmp,
Mov,
}
impl HiOp {
#[must_use]
pub const fn mnemonic(self) -> &'static str {
match self {
HiOp::Add => "ADD",
HiOp::Cmp => "CMP",
HiOp::Mov => "MOV",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MemRegOp {
Str,
Strh,
Strb,
Ldrsb,
Ldr,
Ldrh,
Ldrb,
Ldrsh,
}
impl MemRegOp {
#[must_use]
pub const fn from_bits(bits: u16) -> MemRegOp {
match bits & 0b111 {
0 => MemRegOp::Str,
1 => MemRegOp::Strh,
2 => MemRegOp::Strb,
3 => MemRegOp::Ldrsb,
4 => MemRegOp::Ldr,
5 => MemRegOp::Ldrh,
6 => MemRegOp::Ldrb,
_ => MemRegOp::Ldrsh,
}
}
#[must_use]
pub const fn mnemonic(self) -> &'static str {
match self {
MemRegOp::Str => "STR",
MemRegOp::Strh => "STRH",
MemRegOp::Strb => "STRB",
MemRegOp::Ldrsb => "LDRSB",
MemRegOp::Ldr => "LDR",
MemRegOp::Ldrh => "LDRH",
MemRegOp::Ldrb => "LDRB",
MemRegOp::Ldrsh => "LDRSH",
}
}
#[must_use]
pub const fn is_load(self) -> bool {
matches!(
self,
MemRegOp::Ldrsb | MemRegOp::Ldr | MemRegOp::Ldrh | MemRegOp::Ldrb | MemRegOp::Ldrsh
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MemSize {
Byte,
Half,
Word,
}
impl MemSize {
#[must_use]
pub const fn bytes(self) -> u32 {
match self {
MemSize::Byte => 1,
MemSize::Half => 2,
MemSize::Word => 4,
}
}
#[must_use]
pub const fn suffix(self) -> &'static str {
match self {
MemSize::Byte => "B",
MemSize::Half => "H",
MemSize::Word => "",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Thumb {
ShiftImm {
ty: ShiftType,
rd: u8,
rm: u8,
imm: u8,
},
AddSub {
sub: bool,
rd: u8,
rn: u8,
operand: SmallOperand,
},
AluImm {
op: ImmOp,
rd: u8,
imm: u8,
},
Alu {
op: AluOp,
rd: u8,
rm: u8,
},
HiReg {
op: HiOp,
rd: u8,
rm: u8,
},
BranchExchange {
link: bool,
rm: u8,
},
LoadLiteral {
rd: u8,
imm: u8,
},
MemReg {
op: MemRegOp,
rd: u8,
rn: u8,
rm: u8,
},
MemImm {
load: bool,
size: MemSize,
rd: u8,
rn: u8,
imm: u8,
},
MemStack {
load: bool,
rd: u8,
imm: u8,
},
AddPcSp {
sp: bool,
rd: u8,
imm: u8,
},
AdjustStack {
sub: bool,
imm: u8,
},
PushPop {
load: bool,
extra: bool,
list: u8,
},
BlockTransfer {
load: bool,
rn: u8,
list: u8,
},
BranchCond {
cond: Cond,
offset: i32,
},
Swi {
imm: u8,
},
Bkpt {
imm: u8,
},
Branch {
offset: i32,
},
BranchLinkPrefix {
offset: i32,
},
BranchLinkSuffix {
exchange: bool,
offset: u32,
},
Undefined,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SmallOperand {
Reg(u8),
Imm(u8),
}
impl fmt::Display for SmallOperand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
SmallOperand::Reg(r) => write!(f, "{}", RegName(r)),
SmallOperand::Imm(i) => write!(f, "#{i}"),
}
}
}
#[must_use]
#[allow(clippy::too_many_lines)] pub fn decode(raw: u16) -> Thumb {
let rd = hfield(raw, 2, 0) as u8;
let rn = hfield(raw, 5, 3) as u8;
match hfield(raw, 15, 13) {
0b000 => {
if hfield(raw, 12, 11) == 0b11 {
let sub = bit(u32::from(raw), 9);
let operand = if bit(u32::from(raw), 10) {
SmallOperand::Imm(hfield(raw, 8, 6) as u8)
} else {
SmallOperand::Reg(hfield(raw, 8, 6) as u8)
};
Thumb::AddSub {
sub,
rd,
rn,
operand,
}
} else {
Thumb::ShiftImm {
ty: ShiftType::from_bits(u32::from(hfield(raw, 12, 11))),
rd,
rm: rn,
imm: hfield(raw, 10, 6) as u8,
}
}
}
0b001 => Thumb::AluImm {
op: match hfield(raw, 12, 11) {
0 => ImmOp::Mov,
1 => ImmOp::Cmp,
2 => ImmOp::Add,
_ => ImmOp::Sub,
},
rd: hfield(raw, 10, 8) as u8,
imm: hfield(raw, 7, 0) as u8,
},
0b010 => decode_group_010(raw),
0b011 => Thumb::MemImm {
load: bit(u32::from(raw), 11),
size: if bit(u32::from(raw), 12) {
MemSize::Byte
} else {
MemSize::Word
},
rd,
rn,
imm: hfield(raw, 10, 6) as u8,
},
0b100 => {
if bit(u32::from(raw), 12) {
Thumb::MemStack {
load: bit(u32::from(raw), 11),
rd: hfield(raw, 10, 8) as u8,
imm: hfield(raw, 7, 0) as u8,
}
} else {
Thumb::MemImm {
load: bit(u32::from(raw), 11),
size: MemSize::Half,
rd,
rn,
imm: hfield(raw, 10, 6) as u8,
}
}
}
0b101 => {
if !bit(u32::from(raw), 12) {
Thumb::AddPcSp {
sp: bit(u32::from(raw), 11),
rd: hfield(raw, 10, 8) as u8,
imm: hfield(raw, 7, 0) as u8,
}
} else {
decode_misc(raw)
}
}
0b110 => {
if !bit(u32::from(raw), 12) {
Thumb::BlockTransfer {
load: bit(u32::from(raw), 11),
rn: hfield(raw, 10, 8) as u8,
list: hfield(raw, 7, 0) as u8,
}
} else {
match hfield(raw, 11, 8) {
0b1110 => Thumb::Undefined,
0b1111 => Thumb::Swi {
imm: hfield(raw, 7, 0) as u8,
},
cond => Thumb::BranchCond {
cond: Cond(cond as u8),
offset: i32::from(hfield(raw, 7, 0) as u8 as i8) * 2,
},
}
}
}
_ => decode_branch(raw),
}
}
fn decode_group_010(raw: u16) -> Thumb {
let rd = hfield(raw, 2, 0) as u8;
let rn = hfield(raw, 5, 3) as u8;
if bit(u32::from(raw), 12) {
return Thumb::MemReg {
op: MemRegOp::from_bits(hfield(raw, 11, 9)),
rd,
rn,
rm: hfield(raw, 8, 6) as u8,
};
}
if bit(u32::from(raw), 11) {
return Thumb::LoadLiteral {
rd: hfield(raw, 10, 8) as u8,
imm: hfield(raw, 7, 0) as u8,
};
}
if bit(u32::from(raw), 10) {
let rm = (hfield(raw, 6, 3) as u8) & 0xf;
let hd = rd | if bit(u32::from(raw), 7) { 8 } else { 0 };
return match hfield(raw, 9, 8) {
0b00 => Thumb::HiReg {
op: HiOp::Add,
rd: hd,
rm,
},
0b01 => Thumb::HiReg {
op: HiOp::Cmp,
rd: hd,
rm,
},
0b10 => Thumb::HiReg {
op: HiOp::Mov,
rd: hd,
rm,
},
_ => Thumb::BranchExchange {
link: bit(u32::from(raw), 7),
rm,
},
};
}
Thumb::Alu {
op: AluOp::from_bits(hfield(raw, 9, 6)),
rd,
rm: rn,
}
}
fn decode_misc(raw: u16) -> Thumb {
match hfield(raw, 11, 8) {
0b0000 => Thumb::AdjustStack {
sub: bit(u32::from(raw), 7),
imm: hfield(raw, 6, 0) as u8,
},
0b0100 | 0b0101 | 0b1100 | 0b1101 => Thumb::PushPop {
load: bit(u32::from(raw), 11),
extra: bit(u32::from(raw), 8),
list: hfield(raw, 7, 0) as u8,
},
0b1110 => Thumb::Bkpt {
imm: hfield(raw, 7, 0) as u8,
},
_ => Thumb::Undefined,
}
}
fn decode_branch(raw: u16) -> Thumb {
let imm11 = u32::from(hfield(raw, 10, 0));
match hfield(raw, 12, 11) {
0b00 => Thumb::Branch {
offset: (((imm11 << 21) as i32) >> 21) * 2,
},
0b01 => Thumb::BranchLinkSuffix {
exchange: true,
offset: (imm11 << 1) & !0b11,
},
0b10 => Thumb::BranchLinkPrefix {
offset: ((imm11 << 21) as i32) >> 9,
},
_ => Thumb::BranchLinkSuffix {
exchange: false,
offset: imm11 << 1,
},
}
}
struct RegList {
list: u8,
extra: Option<u8>,
}
impl fmt::Display for RegList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("{")?;
let mut first = true;
let mut i = 0u8;
while i < 8 {
if self.list & (1 << i) == 0 {
i += 1;
continue;
}
let start = i;
while i < 8 && self.list & (1 << i) != 0 {
i += 1;
}
let end = i - 1;
if !first {
f.write_str(", ")?;
}
first = false;
match end - start {
0 => write!(f, "{}", RegName(start))?,
1 => write!(f, "{}, {}", RegName(start), RegName(end))?,
_ => write!(f, "{}-{}", RegName(start), RegName(end))?,
}
}
if let Some(r) = self.extra {
let sep = if first { "" } else { ", " };
write!(f, "{sep}{}", RegName(r))?;
}
f.write_str("}")
}
}
impl fmt::Display for Thumb {
#[allow(clippy::too_many_lines)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Thumb::ShiftImm { ty, rd, rm, imm } => {
let shown = if imm == 0 && ty != ShiftType::Lsl {
32
} else {
u32::from(imm)
};
write!(f, "{ty} {}, {}, #{shown}", RegName(rd), RegName(rm))
}
Thumb::AddSub {
sub,
rd,
rn,
operand,
} => {
let name = if sub { "SUB" } else { "ADD" };
write!(f, "{name} {}, {}, {operand}", RegName(rd), RegName(rn))
}
Thumb::AluImm { op, rd, imm } => {
write!(f, "{} {}, #{imm}", op.mnemonic(), RegName(rd))
}
Thumb::Alu { op, rd, rm } => {
write!(f, "{} {}, {}", op.mnemonic(), RegName(rd), RegName(rm))
}
Thumb::HiReg { op, rd, rm } => {
write!(f, "{} {}, {}", op.mnemonic(), RegName(rd), RegName(rm))
}
Thumb::BranchExchange { link, rm } => {
let name = if link { "BLX" } else { "BX" };
write!(f, "{name} {}", RegName(rm))
}
Thumb::LoadLiteral { rd, imm } => {
write!(f, "LDR {}, [pc, #{}]", RegName(rd), u32::from(imm) * 4)
}
Thumb::MemReg { op, rd, rn, rm } => write!(
f,
"{} {}, [{}, {}]",
op.mnemonic(),
RegName(rd),
RegName(rn),
RegName(rm)
),
Thumb::MemImm {
load,
size,
rd,
rn,
imm,
} => {
let name = if load { "LDR" } else { "STR" };
write!(
f,
"{name}{} {}, [{}, #{}]",
size.suffix(),
RegName(rd),
RegName(rn),
u32::from(imm) * size.bytes()
)
}
Thumb::MemStack { load, rd, imm } => {
let name = if load { "LDR" } else { "STR" };
write!(f, "{name} {}, [sp, #{}]", RegName(rd), u32::from(imm) * 4)
}
Thumb::AddPcSp { sp, rd, imm } => {
let base = if sp { "sp" } else { "pc" };
write!(f, "ADD {}, {base}, #{}", RegName(rd), u32::from(imm) * 4)
}
Thumb::AdjustStack { sub, imm } => {
let name = if sub { "SUB" } else { "ADD" };
write!(f, "{name} sp, #{}", u32::from(imm) * 4)
}
Thumb::PushPop { load, extra, list } => {
let name = if load { "POP" } else { "PUSH" };
let extra = match (extra, load) {
(true, true) => Some(15),
(true, false) => Some(14),
(false, _) => None,
};
write!(f, "{name} {}", RegList { list, extra })
}
Thumb::BlockTransfer { load, rn, list } => {
let name = if load { "LDMIA" } else { "STMIA" };
write!(
f,
"{name} {}!, {}",
RegName(rn),
RegList { list, extra: None }
)
}
Thumb::BranchCond { cond, offset } => write!(f, "B{cond} {offset:+}"),
Thumb::Swi { imm } => write!(f, "SWI #{imm}"),
Thumb::Bkpt { imm } => write!(f, "BKPT #{imm}"),
Thumb::Branch { offset } => write!(f, "B {offset:+}"),
Thumb::BranchLinkPrefix { offset } => write!(f, "BL(prefix) {offset:+}"),
Thumb::BranchLinkSuffix { exchange, offset } => {
let name = if exchange { "BLX" } else { "BL" };
write!(f, "{name}(suffix) +{offset}")
}
Thumb::Undefined => f.write_str("UNDEFINED"),
}
}
}