use alloc::string::String;
use crate::encoder::{EncodedInstr, InstrBytes, RelocKind, Relocation};
use crate::error::AsmError;
use crate::ir::*;
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
enum Cond {
Eq = 0x0,
Ne = 0x1,
Cs = 0x2, Cc = 0x3, Mi = 0x4,
Pl = 0x5,
Vs = 0x6,
Vc = 0x7,
Hi = 0x8,
Ls = 0x9,
Ge = 0xA,
Lt = 0xB,
Gt = 0xC,
Le = 0xD,
Al = 0xE,
}
fn parse_cond(mnemonic: &str) -> (&str, Cond) {
if mnemonic.len() < 4 {
if mnemonic.len() == 3 {
let (base, suffix) = mnemonic.split_at(1);
if base == "b" {
return match suffix {
"eq" => (base, Cond::Eq),
"ne" => (base, Cond::Ne),
"cs" | "hs" => (base, Cond::Cs),
"cc" | "lo" => (base, Cond::Cc),
"mi" => (base, Cond::Mi),
"pl" => (base, Cond::Pl),
"vs" => (base, Cond::Vs),
"vc" => (base, Cond::Vc),
"hi" => (base, Cond::Hi),
"ls" => (base, Cond::Ls),
"ge" => (base, Cond::Ge),
"lt" => (base, Cond::Lt),
"gt" => (base, Cond::Gt),
"le" => (base, Cond::Le),
"al" => (base, Cond::Al),
_ => (mnemonic, Cond::Al),
};
}
}
return (mnemonic, Cond::Al);
}
if is_known_base(mnemonic) {
return (mnemonic, Cond::Al);
}
let (base, suffix) = mnemonic.split_at(mnemonic.len() - 2);
match suffix {
"eq" => (base, Cond::Eq),
"ne" => (base, Cond::Ne),
"cs" | "hs" => (base, Cond::Cs),
"cc" | "lo" => (base, Cond::Cc),
"mi" => (base, Cond::Mi),
"pl" => (base, Cond::Pl),
"vs" => (base, Cond::Vs),
"vc" => (base, Cond::Vc),
"hi" => (base, Cond::Hi),
"ls" => (base, Cond::Ls),
"ge" => (base, Cond::Ge),
"lt" => (base, Cond::Lt),
"gt" => (base, Cond::Gt),
"le" => (base, Cond::Le),
"al" => (base, Cond::Al),
_ => (mnemonic, Cond::Al),
}
}
fn is_known_base(m: &str) -> bool {
let core = if m.ends_with('s') && m.len() > 1 {
&m[..m.len() - 1]
} else {
m
};
matches!(
core,
"and" | "eor" | "sub" | "rsb" | "add" | "adc" | "sbc" | "rsc"
| "tst" | "teq" | "cmp" | "cmn" | "orr" | "mov" | "bic" | "mvn"
| "mul" | "mla" | "umull" | "smull" | "umlal" | "smlal"
| "ldr" | "str" | "ldrb" | "strb" | "ldrh" | "strh" | "ldrsb" | "ldrsh"
| "ldrex" | "ldrexb" | "ldrexh" | "ldrexd"
| "strex" | "strexb" | "strexh" | "strexd"
| "ldm" | "ldmia" | "ldmfd" | "ldmdb" | "ldmea" | "ldmib" | "ldmed"
| "ldmda" | "ldmfa" | "stm" | "stmia" | "stmea" | "stmdb" | "stmfd"
| "stmib" | "stmfa" | "stmda" | "stmed"
| "b" | "bl" | "bx" | "blx"
| "push" | "pop"
| "nop" | "bkpt" | "svc" | "swi" | "adr" | "clz"
| "rev" | "rev16" | "rbit"
| "bfc" | "bfi" | "sbfx" | "ubfx"
| "uxtb" | "uxth" | "sxtb" | "sxth"
| "mrs" | "msr"
| "movw" | "movt"
| "dmb" | "dsb" | "isb"
)
}
fn invalid_ops(mnemonic: &str, detail: &str, span: crate::error::Span) -> AsmError {
AsmError::InvalidOperands {
detail: alloc::format!("{}: {}", mnemonic, detail),
span,
}
}
fn get_arm_reg(
op: &Operand,
mnemonic: &str,
span: crate::error::Span,
) -> Result<Register, AsmError> {
match op {
Operand::Register(r) if r.is_arm() => Ok(*r),
_ => Err(invalid_ops(mnemonic, "expected ARM register", span)),
}
}
fn get_imm(op: &Operand, mnemonic: &str, span: crate::error::Span) -> Result<i128, AsmError> {
match op {
Operand::Immediate(v) => Ok(*v),
_ => Err(invalid_ops(mnemonic, "expected immediate", span)),
}
}
fn encode_arm_imm(value: u32) -> Option<(u8, u8)> {
for rot in 0..16u8 {
let shift = rot * 2;
let rotated = value.rotate_left(shift as u32);
if rotated <= 0xFF {
return Some((rotated as u8, rot));
}
}
None
}
fn reg_list_mask(regs: &[Register]) -> u16 {
let mut mask = 0u16;
for r in regs {
mask |= 1 << r.arm_reg_num();
}
mask
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
enum ShiftType {
Lsl = 0b00,
Lsr = 0b01,
Asr = 0b10,
Ror = 0b11,
}
#[derive(Debug, Clone, Copy)]
struct ShiftInfo {
shift_type: ShiftType,
amount: u8, reg_shift: Option<Register>, }
fn parse_shift(
ops: &OperandList,
idx: usize,
mnemonic: &str,
span: crate::error::Span,
) -> Result<Option<(ShiftInfo, usize)>, AsmError> {
let Some(Operand::Shift(op, amount)) = ops.get(idx) else {
return Ok(None);
};
if *op == ShiftOp::Rrx {
if !matches!(amount, ShiftAmount::None) {
return Err(invalid_ops(mnemonic, "rrx does not take an amount", span));
}
return Ok(Some((
ShiftInfo {
shift_type: ShiftType::Ror,
amount: 0,
reg_shift: None,
},
1,
)));
}
let shift_type = match op {
ShiftOp::Lsl => ShiftType::Lsl,
ShiftOp::Lsr => ShiftType::Lsr,
ShiftOp::Asr => ShiftType::Asr,
ShiftOp::Ror => ShiftType::Ror,
_ => {
return Err(invalid_ops(
mnemonic,
"register extends are AArch64-only; expected lsl, lsr, asr, ror or rrx",
span,
))
}
};
match amount {
ShiftAmount::Immediate(v) => {
let max = if matches!(shift_type, ShiftType::Lsr | ShiftType::Asr) {
32
} else {
31
};
if !(0..=max).contains(v) {
return Err(invalid_ops(
mnemonic,
"shift amount out of range for this shift type",
span,
));
}
Ok(Some((
ShiftInfo {
shift_type,
amount: (*v as u8) & 0x1F,
reg_shift: None,
},
1,
)))
}
ShiftAmount::Register(rs) if rs.is_arm() => Ok(Some((
ShiftInfo {
shift_type,
amount: 0,
reg_shift: Some(*rs),
},
1,
))),
ShiftAmount::Register(_) => Err(invalid_ops(
mnemonic,
"shift amount register must be an ARM32 register",
span,
)),
ShiftAmount::None => Err(invalid_ops(
mnemonic,
"shift requires an amount (`lsl #3`) or a register (`lsl r3`)",
span,
)),
}
}
#[allow(clippy::too_many_arguments)]
fn encode_shift_alias(
buf: &mut InstrBytes,
cond: Cond,
base: &str,
ops: &OperandList,
instr: &Instruction,
set_flags: bool,
) -> Result<(), AsmError> {
let is_rrx = base == "rrx";
let expected = if is_rrx { 2 } else { 3 };
if ops.len() != expected {
return Err(invalid_ops(
base,
if is_rrx {
"expected 2 operands (Rd, Rm)"
} else {
"expected 3 operands (Rd, Rm, #imm or Rs)"
},
instr.span,
));
}
let rd = get_arm_reg(&ops[0], base, instr.span)?;
let rm = get_arm_reg(&ops[1], base, instr.span)?;
let shift_op = ShiftOp::from_lower(base)
.ok_or_else(|| invalid_ops(base, "unknown shift mnemonic", instr.span))?;
let amount = if is_rrx {
ShiftAmount::None
} else {
match &ops[2] {
Operand::Immediate(v) => {
ShiftAmount::Immediate((*v).clamp(i128::from(i32::MIN), i128::from(i32::MAX)) as i32)
}
Operand::Register(r) => ShiftAmount::Register(*r),
_ => {
return Err(invalid_ops(
base,
"shift amount must be an immediate or a register",
instr.span,
))
}
}
};
let mut rewritten = OperandList::new();
rewritten.push(Operand::Register(rd));
rewritten.push(Operand::Register(rm));
rewritten.push(Operand::Shift(shift_op, amount));
encode_dp(buf, cond, "mov", &rewritten, instr, set_flags)
}
#[inline]
fn encode_shifted_reg(rm: u8, shift: Option<ShiftInfo>) -> u32 {
let rm = rm as u32;
match shift {
None => rm, Some(si) => {
let stype = (si.shift_type as u32) & 0x3;
if let Some(rs) = si.reg_shift {
let rs_num = rs.arm_reg_num() as u32;
(rs_num << 8) | (stype << 5) | (1 << 4) | rm
} else {
let imm5 = (si.amount as u32) & 0x1F;
(imm5 << 7) | (stype << 5) | rm
}
}
}
}
#[inline]
fn emit32(buf: &mut InstrBytes, word: u32) {
buf.extend_from_slice(&word.to_le_bytes());
}
fn dp_opcode(mnemonic: &str) -> Option<u8> {
match mnemonic {
"and" => Some(0x0),
"eor" => Some(0x1),
"sub" => Some(0x2),
"rsb" => Some(0x3),
"add" => Some(0x4),
"adc" => Some(0x5),
"sbc" => Some(0x6),
"rsc" => Some(0x7),
"tst" => Some(0x8),
"teq" => Some(0x9),
"cmp" => Some(0xA),
"cmn" => Some(0xB),
"orr" => Some(0xC),
"mov" => Some(0xD),
"bic" => Some(0xE),
"mvn" => Some(0xF),
_ => None,
}
}
fn encode_dp(
buf: &mut InstrBytes,
cond: Cond,
base: &str,
ops: &OperandList,
instr: &Instruction,
set_flags: bool,
) -> Result<(), AsmError> {
let opcode = dp_opcode(base)
.ok_or_else(|| invalid_ops(base, "unknown data processing opcode", instr.span))?;
let is_test = matches!(base, "tst" | "teq" | "cmp" | "cmn");
let (rd, rn, op2_idx) = if is_test {
if ops.len() < 2 {
return Err(invalid_ops(base, "expected 2 operands", instr.span));
}
let rn = get_arm_reg(&ops[0], base, instr.span)?;
(Register::ArmR0, rn, 1)
} else if matches!(base, "mov" | "mvn") {
if ops.len() < 2 {
return Err(invalid_ops(base, "expected 2 operands", instr.span));
}
let rd = get_arm_reg(&ops[0], base, instr.span)?;
(rd, Register::ArmR0, 1)
} else {
if ops.len() < 3 {
return Err(invalid_ops(
base,
"expected 3 operands (Rd, Rn, operand2)",
instr.span,
));
}
let rd = get_arm_reg(&ops[0], base, instr.span)?;
let rn = get_arm_reg(&ops[1], base, instr.span)?;
(rd, rn, 2)
};
let s = if is_test || set_flags { 1u32 } else { 0u32 };
match &ops[op2_idx] {
Operand::Register(rm) if rm.is_arm() => {
let shift = parse_shift(ops, op2_idx + 1, base, instr.span)?.map(|(si, _)| si);
let operand2 = encode_shifted_reg(rm.arm_reg_num(), shift);
#[allow(clippy::identity_op)]
let word = ((cond as u32) << 28)
| (0b00 << 26)
| ((opcode as u32) << 21)
| (s << 20)
| ((rn.arm_reg_num() as u32) << 16)
| ((rd.arm_reg_num() as u32) << 12)
| operand2;
emit32(buf, word);
}
Operand::Immediate(imm) => {
let val = *imm as u32;
match encode_arm_imm(val) {
Some((imm8, rot)) => {
let word = ((cond as u32) << 28)
| (0b001 << 25)
| ((opcode as u32) << 21)
| (s << 20)
| ((rn.arm_reg_num() as u32) << 16)
| ((rd.arm_reg_num() as u32) << 12)
| ((rot as u32) << 8)
| (imm8 as u32);
emit32(buf, word);
}
None if matches!(base, "mov" | "mvn") => {
let effective = if base == "mvn" { !val } else { val };
let lo16 = effective & 0xFFFF;
let hi16 = (effective >> 16) & 0xFFFF;
let imm4_lo = (lo16 >> 12) & 0xF;
let imm12_lo = lo16 & 0xFFF;
let movw = ((cond as u32) << 28)
| (0b0011_0000 << 20)
| (imm4_lo << 16)
| ((rd.arm_reg_num() as u32) << 12)
| imm12_lo;
emit32(buf, movw);
if hi16 != 0 {
let imm4_hi = (hi16 >> 12) & 0xF;
let imm12_hi = hi16 & 0xFFF;
let movt = ((cond as u32) << 28)
| (0b0011_0100 << 20)
| (imm4_hi << 16)
| ((rd.arm_reg_num() as u32) << 12)
| imm12_hi;
emit32(buf, movt);
}
}
None => {
return Err(invalid_ops(
base,
"immediate value cannot be encoded as ARM rotated immediate",
instr.span,
));
}
}
}
_ => {
return Err(invalid_ops(
base,
"operand2 must be register or immediate",
instr.span,
))
}
}
Ok(())
}
fn encode_ldr_str(
buf: &mut InstrBytes,
cond: Cond,
base: &str,
ops: &OperandList,
instr: &Instruction,
reloc: &mut Option<Relocation>,
) -> Result<(), AsmError> {
if ops.len() < 2 {
return Err(invalid_ops(
base,
"expected register and memory operand",
instr.span,
));
}
let rd = get_arm_reg(&ops[0], base, instr.span)?;
let is_load = matches!(base, "ldr" | "ldrb" | "ldrh" | "ldrsb" | "ldrsh");
let is_byte = matches!(base, "ldrb" | "strb");
let post_index_imm = if ops.len() >= 3 {
match &ops[1] {
Operand::Memory(m)
if m.addr_mode == AddrMode::Offset && m.disp == 0 && m.index.is_none() =>
{
match &ops[2] {
Operand::Immediate(v) => Some(*v as i64),
_ => None,
}
}
_ => None,
}
} else {
None
};
if let Some(offset) = post_index_imm {
let mem = match &ops[1] {
Operand::Memory(m) => m,
_ => {
return Err(invalid_ops(
base,
"expected memory operand for post-index addressing",
instr.span,
))
}
};
let rn = match mem.base {
Some(r) if r.is_arm() => r,
_ => {
return Err(invalid_ops(
base,
"memory base must be ARM register",
instr.span,
))
}
};
let u = if offset >= 0 { 1u32 } else { 0u32 };
let abs_off = (offset.unsigned_abs() as u32) & 0xFFF;
let b = is_byte as u32;
let l = is_load as u32;
#[allow(clippy::identity_op)]
let word = ((cond as u32) << 28)
| (0b010 << 25)
| (0u32 << 24) | (u << 23)
| (b << 22)
| (0u32 << 21) | (l << 20)
| ((rn.arm_reg_num() as u32) << 16)
| ((rd.arm_reg_num() as u32) << 12)
| abs_off;
emit32(buf, word);
return Ok(());
}
match &ops[1] {
Operand::Memory(mem) => {
let rn = match mem.base {
Some(r) if r.is_arm() => r,
_ => {
return Err(invalid_ops(
base,
"memory base must be ARM register",
instr.span,
))
}
};
let is_preindex = mem.addr_mode == AddrMode::PreIndex;
let w = is_preindex as u32;
if let Some(rm) = mem.index {
if !rm.is_arm() {
return Err(invalid_ops(
base,
"memory index must be ARM register",
instr.span,
));
}
let u = if mem.index_subtract { 0u32 } else { 1u32 };
let p = 1u32; let b = is_byte as u32;
let l = is_load as u32;
let word = ((cond as u32) << 28)
| (0b011 << 25)
| (p << 24)
| (u << 23)
| (b << 22)
| (w << 21)
| (l << 20)
| ((rn.arm_reg_num() as u32) << 16)
| ((rd.arm_reg_num() as u32) << 12)
| (rm.arm_reg_num() as u32);
emit32(buf, word);
} else {
let offset = mem.disp;
let u = if offset >= 0 { 1u32 } else { 0u32 };
let abs_off = (offset.unsigned_abs() as u32) & 0xFFF;
if mem.disp_label.is_some() {
let p = 1u32;
let b = is_byte as u32;
let l = is_load as u32;
let word = ((cond as u32) << 28)
| (0b010 << 25)
| (p << 24)
| (1u32 << 23) | (b << 22)
| (w << 21)
| (l << 20)
| ((rn.arm_reg_num() as u32) << 16)
| ((rd.arm_reg_num() as u32) << 12);
let reloc_offset = buf.len();
emit32(buf, word);
if let Some(ref label) = mem.disp_label {
*reloc = Some(Relocation {
offset: reloc_offset,
size: 4,
label: alloc::rc::Rc::from(&**label),
kind: RelocKind::ArmLdrLit,
addend: mem.disp,
trailing_bytes: 0,
});
}
} else {
let p = 1u32; let b = is_byte as u32;
let l = is_load as u32;
let word = ((cond as u32) << 28)
| (0b010 << 25)
| (p << 24)
| (u << 23)
| (b << 22)
| (w << 21)
| (l << 20)
| ((rn.arm_reg_num() as u32) << 16)
| ((rd.arm_reg_num() as u32) << 12)
| abs_off;
emit32(buf, word);
}
}
}
Operand::Label(label) => {
let l = is_load as u32;
let b = is_byte as u32;
let word = ((cond as u32) << 28)
| (0b010 << 25)
| (1u32 << 24) | (1u32 << 23) | (b << 22)
| (l << 20)
| (15u32 << 16) | ((rd.arm_reg_num() as u32) << 12);
let reloc_offset = buf.len();
emit32(buf, word);
*reloc = Some(Relocation {
offset: reloc_offset,
size: 4,
label: alloc::rc::Rc::from(&**label),
kind: RelocKind::ArmLdrLit,
addend: 0,
trailing_bytes: 0,
});
}
_ => {
return Err(invalid_ops(
base,
"expected memory operand or label",
instr.span,
))
}
}
Ok(())
}
fn encode_branch(
buf: &mut InstrBytes,
cond: Cond,
base: &str,
ops: &OperandList,
instr: &Instruction,
reloc: &mut Option<Relocation>,
) -> Result<(), AsmError> {
if ops.len() != 1 {
return Err(invalid_ops(base, "expected 1 operand", instr.span));
}
let is_link = base == "bl";
match &ops[0] {
Operand::Label(label) => {
let l = is_link as u32;
let word = ((cond as u32) << 28) | (0b101 << 25) | (l << 24);
let reloc_offset = buf.len();
emit32(buf, word);
*reloc = Some(Relocation {
offset: reloc_offset,
size: 4,
label: alloc::rc::Rc::from(&**label),
kind: RelocKind::ArmBranch24,
addend: 0,
trailing_bytes: 0,
});
}
Operand::Immediate(imm) => {
let offset = (*imm as i32) >> 2; let imm24 = (offset as u32) & 0x00FF_FFFF;
let l = is_link as u32;
let word = ((cond as u32) << 28) | (0b101 << 25) | (l << 24) | imm24;
emit32(buf, word);
}
Operand::Register(reg) if reg.is_arm() => {
if is_link {
let word = ((cond as u32) << 28) | (0x12FFF30) | (reg.arm_reg_num() as u32);
emit32(buf, word);
} else {
let word = ((cond as u32) << 28) | (0x12FFF10) | (reg.arm_reg_num() as u32);
emit32(buf, word);
}
}
_ => {
return Err(invalid_ops(
base,
"expected label, immediate, or register",
instr.span,
))
}
}
Ok(())
}
fn encode_push_pop(
buf: &mut InstrBytes,
cond: Cond,
base: &str,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
if ops.len() != 1 {
return Err(invalid_ops(base, "expected register list", instr.span));
}
let mask = match &ops[0] {
Operand::RegisterList(regs) => reg_list_mask(regs),
Operand::Register(r) if r.is_arm() => 1u16 << r.arm_reg_num(),
_ => return Err(invalid_ops(base, "expected register list", instr.span)),
};
if mask == 0 {
return Err(invalid_ops(
base,
"register list cannot be empty",
instr.span,
));
}
match base {
"push" => {
#[allow(clippy::identity_op)]
let word = ((cond as u32) << 28)
| (0b100 << 25)
| (1 << 24) | (0 << 23) | (0 << 22) | (1 << 21) | (0 << 20) | (13u32 << 16) | (mask as u32);
emit32(buf, word);
}
"pop" => {
#[allow(clippy::identity_op)]
let word = ((cond as u32) << 28)
| (0b100 << 25)
| (0 << 24) | (1 << 23) | (0 << 22) | (1 << 21) | (1 << 20) | (13u32 << 16) | (mask as u32);
emit32(buf, word);
}
_ => return Err(invalid_ops(base, "internal error", instr.span)),
}
Ok(())
}
fn encode_mul(
buf: &mut InstrBytes,
cond: Cond,
base: &str,
ops: &OperandList,
instr: &Instruction,
set_flags: bool,
) -> Result<(), AsmError> {
let s = set_flags as u32;
match base {
"mul" => {
if ops.len() != 3 {
return Err(invalid_ops("mul", "expected 3 operands", instr.span));
}
let rd = get_arm_reg(&ops[0], "mul", instr.span)?;
let rm = get_arm_reg(&ops[1], "mul", instr.span)?;
let rs = get_arm_reg(&ops[2], "mul", instr.span)?;
let word = ((cond as u32) << 28)
| (s << 20)
| ((rd.arm_reg_num() as u32) << 16)
| ((rs.arm_reg_num() as u32) << 8)
| (0b1001 << 4)
| (rm.arm_reg_num() as u32);
emit32(buf, word);
}
"mla" => {
if ops.len() != 4 {
return Err(invalid_ops("mla", "expected 4 operands", instr.span));
}
let rd = get_arm_reg(&ops[0], "mla", instr.span)?;
let rm = get_arm_reg(&ops[1], "mla", instr.span)?;
let rs = get_arm_reg(&ops[2], "mla", instr.span)?;
let rn = get_arm_reg(&ops[3], "mla", instr.span)?;
let word = ((cond as u32) << 28)
| (1 << 21) | (s << 20)
| ((rd.arm_reg_num() as u32) << 16)
| ((rn.arm_reg_num() as u32) << 12)
| ((rs.arm_reg_num() as u32) << 8)
| (0b1001 << 4)
| (rm.arm_reg_num() as u32);
emit32(buf, word);
}
_ => {
return Err(invalid_ops(
base,
"unknown multiply instruction",
instr.span,
))
}
}
Ok(())
}
fn encode_long_mul(
buf: &mut InstrBytes,
cond: Cond,
base: &str,
ops: &OperandList,
instr: &Instruction,
set_flags: bool,
) -> Result<(), AsmError> {
if ops.len() != 4 {
return Err(invalid_ops(
base,
"expected 4 operands: RdLo, RdHi, Rm, Rs",
instr.span,
));
}
let rd_lo = get_arm_reg(&ops[0], base, instr.span)?;
let rd_hi = get_arm_reg(&ops[1], base, instr.span)?;
let rm = get_arm_reg(&ops[2], base, instr.span)?;
let rs = get_arm_reg(&ops[3], base, instr.span)?;
let s = set_flags as u32;
let (u_bit, a_bit) = match base {
"umull" => (1u32, 0u32),
"smull" => (0u32, 0u32),
"umlal" => (1u32, 1u32),
"smlal" => (0u32, 1u32),
_ => {
return Err(invalid_ops(
base,
"unknown long multiply instruction",
instr.span,
))
}
};
let word = ((cond as u32) << 28)
| (u_bit << 22)
| (a_bit << 21)
| (s << 20)
| ((rd_hi.arm_reg_num() as u32) << 16)
| ((rd_lo.arm_reg_num() as u32) << 12)
| ((rs.arm_reg_num() as u32) << 8)
| (0b1001 << 4)
| (rm.arm_reg_num() as u32);
emit32(buf, word);
Ok(())
}
fn encode_movw_movt(
buf: &mut InstrBytes,
cond: Cond,
base: &str,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
if ops.len() != 2 {
return Err(invalid_ops(
base,
"expected 2 operands (Rd, #imm16)",
instr.span,
));
}
let rd = get_arm_reg(&ops[0], base, instr.span)?;
let imm = get_imm(&ops[1], base, instr.span)? as u32;
if imm > 0xFFFF {
return Err(invalid_ops(
base,
"immediate must fit in 16 bits",
instr.span,
));
}
let imm4 = (imm >> 12) & 0xF;
let imm12 = imm & 0xFFF;
let is_top = base == "movt";
let word = ((cond as u32) << 28)
| (0b0011 << 24)
| ((is_top as u32) << 22)
| (imm4 << 16)
| ((rd.arm_reg_num() as u32) << 12)
| imm12;
emit32(buf, word);
Ok(())
}
fn encode_svc(
buf: &mut InstrBytes,
cond: Cond,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
if ops.len() != 1 {
return Err(invalid_ops("svc", "expected immediate operand", instr.span));
}
let imm = get_imm(&ops[0], "svc", instr.span)? as u32;
if imm > 0x00FF_FFFF {
return Err(invalid_ops(
"svc",
"SVC number must fit in 24 bits",
instr.span,
));
}
let word = ((cond as u32) << 28) | (0xF << 24) | imm;
emit32(buf, word);
Ok(())
}
fn encode_nop(buf: &mut InstrBytes, cond: Cond) {
let word = ((cond as u32) << 28) | 0x01A0_0000;
emit32(buf, word);
}
fn encode_bkpt(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
let imm = if ops.is_empty() {
0u32
} else {
get_imm(&ops[0], "bkpt", instr.span)? as u32
};
if imm > 0xFFFF {
return Err(invalid_ops(
"bkpt",
"BKPT number must fit in 16 bits",
instr.span,
));
}
let imm12 = (imm >> 4) & 0xFFF;
let imm4 = imm & 0xF;
let word = 0xE120_0070 | (imm12 << 8) | imm4;
emit32(buf, word);
Ok(())
}
fn encode_bx(
buf: &mut InstrBytes,
cond: Cond,
is_link: bool,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
if ops.len() != 1 {
let name = if is_link { "blx" } else { "bx" };
return Err(invalid_ops(name, "expected 1 register operand", instr.span));
}
let rm = get_arm_reg(&ops[0], if is_link { "blx" } else { "bx" }, instr.span)?;
if is_link {
let word = ((cond as u32) << 28) | 0x012F_FF30 | (rm.arm_reg_num() as u32);
emit32(buf, word);
} else {
let word = ((cond as u32) << 28) | 0x012F_FF10 | (rm.arm_reg_num() as u32);
emit32(buf, word);
}
Ok(())
}
fn encode_ldm_stm(
buf: &mut InstrBytes,
cond: Cond,
base: &str,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
if ops.len() != 2 {
return Err(invalid_ops(
base,
"expected base register and register list",
instr.span,
));
}
let (rn, writeback) = match &ops[0] {
Operand::Register(r) if r.is_arm() => (*r, false),
Operand::Memory(m) if m.addr_mode == AddrMode::PreIndex => {
match m.base {
Some(r) if r.is_arm() => (r, true),
_ => return Err(invalid_ops(base, "expected ARM base register", instr.span)),
}
}
_ => return Err(invalid_ops(base, "expected ARM base register", instr.span)),
};
let mask = match &ops[1] {
Operand::RegisterList(regs) => reg_list_mask(regs),
_ => return Err(invalid_ops(base, "expected register list", instr.span)),
};
let is_load = matches!(
base,
"ldm" | "ldmia" | "ldmfd" | "ldmdb" | "ldmea" | "ldmib" | "ldmed" | "ldmda" | "ldmfa"
);
let (p, u) = match base {
"ldm" | "ldmia" | "ldmfd" | "stm" | "stmia" | "stmea" => (0, 1), "ldmdb" | "ldmea" | "stmdb" | "stmfd" => (1, 0), "ldmib" | "ldmed" | "stmib" | "stmfa" => (1, 1), "ldmda" | "ldmfa" | "stmda" | "stmed" => (0, 0), _ => (0, 1), };
let w = writeback as u32;
#[allow(clippy::identity_op)]
let word = ((cond as u32) << 28)
| (0b100 << 25)
| ((p as u32) << 24)
| ((u as u32) << 23)
| (0 << 22) | (w << 21)
| ((is_load as u32) << 20)
| ((rn.arm_reg_num() as u32) << 16)
| (mask as u32);
emit32(buf, word);
Ok(())
}
fn encode_ldr_str_h(
buf: &mut InstrBytes,
cond: Cond,
base: &str,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
if ops.len() < 2 {
return Err(invalid_ops(
base,
"expected register and memory operand",
instr.span,
));
}
let rd = get_arm_reg(&ops[0], base, instr.span)?;
let is_load = matches!(base, "ldrh" | "ldrsb" | "ldrsh");
let (s_bit, h_bit) = match base {
"ldrh" | "strh" => (0u32, 1u32),
"ldrsb" => (1, 0),
"ldrsh" => (1, 1),
_ => return Err(invalid_ops(base, "internal error", instr.span)),
};
let l = is_load as u32;
let post_index_imm = if ops.len() >= 3 {
match &ops[1] {
Operand::Memory(m)
if m.addr_mode == AddrMode::Offset && m.disp == 0 && m.index.is_none() =>
{
match &ops[2] {
Operand::Immediate(v) => Some(*v as i64),
_ => None,
}
}
_ => None,
}
} else {
None
};
if let Some(offset) = post_index_imm {
let mem = match &ops[1] {
Operand::Memory(m) => m,
_ => return Err(invalid_ops(base, "expected memory operand", instr.span)),
};
let rn = match mem.base {
Some(r) if r.is_arm() => r,
_ => {
return Err(invalid_ops(
base,
"memory base must be ARM register",
instr.span,
))
}
};
let u = if offset >= 0 { 1u32 } else { 0u32 };
let abs_off = offset.unsigned_abs() as u32;
if abs_off > 255 {
return Err(invalid_ops(
base,
"halfword immediate offset must fit in 8 bits",
instr.span,
));
}
let imm4h = (abs_off >> 4) & 0xF;
let imm4l = abs_off & 0xF;
#[allow(clippy::identity_op)]
let word = ((cond as u32) << 28)
| (0u32 << 24) | (u << 23)
| (1 << 22) | (0u32 << 21) | (l << 20)
| ((rn.arm_reg_num() as u32) << 16)
| ((rd.arm_reg_num() as u32) << 12)
| (imm4h << 8)
| (1 << 7)
| (s_bit << 6)
| (h_bit << 5)
| (1 << 4)
| imm4l;
emit32(buf, word);
return Ok(());
}
let mem = match &ops[1] {
Operand::Memory(m) => m,
_ => return Err(invalid_ops(base, "expected memory operand", instr.span)),
};
let rn = match mem.base {
Some(r) if r.is_arm() => r,
_ => {
return Err(invalid_ops(
base,
"memory base must be ARM register",
instr.span,
))
}
};
let is_preindex = mem.addr_mode == AddrMode::PreIndex;
let w = is_preindex as u32;
if let Some(rm) = mem.index {
if !rm.is_arm() {
return Err(invalid_ops(
base,
"memory index must be ARM register",
instr.span,
));
}
let u = if mem.index_subtract { 0u32 } else { 1u32 };
let word = ((cond as u32) << 28)
| (1 << 24) | (u << 23) | (w << 21) | (l << 20)
| ((rn.arm_reg_num() as u32) << 16)
| ((rd.arm_reg_num() as u32) << 12)
| (1 << 7)
| (s_bit << 6)
| (h_bit << 5)
| (1 << 4)
| (rm.arm_reg_num() as u32);
emit32(buf, word);
} else {
let offset = mem.disp;
let u = if offset >= 0 { 1u32 } else { 0u32 };
let abs_off = offset.unsigned_abs() as u32;
if abs_off > 255 {
return Err(invalid_ops(
base,
"halfword immediate offset must fit in 8 bits",
instr.span,
));
}
let imm4h = (abs_off >> 4) & 0xF;
let imm4l = abs_off & 0xF;
let word = ((cond as u32) << 28)
| (1 << 24) | (u << 23)
| (1 << 22) | (w << 21) | (l << 20)
| ((rn.arm_reg_num() as u32) << 16)
| ((rd.arm_reg_num() as u32) << 12)
| (imm4h << 8)
| (1 << 7)
| (s_bit << 6)
| (h_bit << 5)
| (1 << 4)
| imm4l;
emit32(buf, word);
}
Ok(())
}
fn encode_adr(
buf: &mut InstrBytes,
cond: Cond,
ops: &OperandList,
instr: &Instruction,
reloc: &mut Option<Relocation>,
) -> Result<(), AsmError> {
if ops.len() != 2 {
return Err(invalid_ops(
"adr",
"expected Rd, label_or_immediate",
instr.span,
));
}
let rd = get_arm_reg(&ops[0], "adr", instr.span)?;
match &ops[1] {
Operand::Label(label) => {
let word = ((cond as u32) << 28)
| (0b001 << 25) | (0x4 << 21) | (15u32 << 16) | ((rd.arm_reg_num() as u32) << 12);
let reloc_offset = buf.len();
emit32(buf, word);
*reloc = Some(Relocation {
offset: reloc_offset,
size: 4,
label: alloc::rc::Rc::from(&**label),
kind: RelocKind::ArmAdr,
addend: 0,
trailing_bytes: 0,
});
}
Operand::Immediate(imm) => {
let offset = *imm as i32;
let (op, abs_val) = if offset >= 0 {
(0x4u32, offset as u32) } else {
(0x2u32, (-offset) as u32) };
let (imm8, rot) = encode_arm_imm(abs_val).ok_or_else(|| {
invalid_ops(
"adr",
"offset cannot be encoded as ARM rotated immediate",
instr.span,
)
})?;
let word = ((cond as u32) << 28)
| (0b001 << 25)
| (op << 21)
| (15u32 << 16) | ((rd.arm_reg_num() as u32) << 12)
| ((rot as u32) << 8)
| (imm8 as u32);
emit32(buf, word);
}
_ => {
return Err(invalid_ops(
"adr",
"expected label or immediate",
instr.span,
))
}
}
Ok(())
}
fn encode_clz(
buf: &mut InstrBytes,
cond: Cond,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
if ops.len() != 2 {
return Err(invalid_ops("clz", "expected Rd, Rm", instr.span));
}
let rd = get_arm_reg(&ops[0], "clz", instr.span)?;
let rm = get_arm_reg(&ops[1], "clz", instr.span)?;
let word = ((cond as u32) << 28)
| (0b00010110 << 20)
| (0xF << 16) | ((rd.arm_reg_num() as u32) << 12)
| (0xF << 8) | (0b0001 << 4)
| (rm.arm_reg_num() as u32);
emit32(buf, word);
Ok(())
}
fn encode_rev(
buf: &mut InstrBytes,
cond: Cond,
mnemonic: &str,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
if ops.len() != 2 {
return Err(invalid_ops(mnemonic, "expected Rd, Rm", instr.span));
}
let rd = get_arm_reg(&ops[0], mnemonic, instr.span)?;
let rm = get_arm_reg(&ops[1], mnemonic, instr.span)?;
let (op1, op2) = match mnemonic {
"rev" => (0b01101011u32, 0b0011u32),
"rev16" => (0b01101011u32, 0b1011u32),
"revsh" => (0b01101111u32, 0b1011u32),
"rbit" => (0b01101111u32, 0b0011u32),
_ => return Err(invalid_ops(mnemonic, "internal error", instr.span)),
};
let word = ((cond as u32) << 28)
| (op1 << 20)
| (0xF << 16)
| ((rd.arm_reg_num() as u32) << 12)
| (0xF << 8)
| (op2 << 4)
| (rm.arm_reg_num() as u32);
emit32(buf, word);
Ok(())
}
fn encode_bitfield(
buf: &mut InstrBytes,
cond: Cond,
mnemonic: &str,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
let word = match mnemonic {
"bfc" => {
if ops.len() != 3 {
return Err(invalid_ops("bfc", "expected Rd, #lsb, #width", instr.span));
}
let rd = get_arm_reg(&ops[0], "bfc", instr.span)?;
let lsb = get_imm(&ops[1], "bfc", instr.span)? as u32;
let width = get_imm(&ops[2], "bfc", instr.span)? as u32;
if lsb > 31 || width == 0 || width > 32 || lsb + width > 32 {
return Err(invalid_ops("bfc", "lsb/width out of range", instr.span));
}
let msb = lsb + width - 1;
((cond as u32) << 28)
| (0b0111110 << 21)
| (msb << 16)
| ((rd.arm_reg_num() as u32) << 12)
| (lsb << 7)
| (0b001 << 4)
| 0b1111
}
"bfi" => {
if ops.len() != 4 {
return Err(invalid_ops(
"bfi",
"expected Rd, Rn, #lsb, #width",
instr.span,
));
}
let rd = get_arm_reg(&ops[0], "bfi", instr.span)?;
let rn = get_arm_reg(&ops[1], "bfi", instr.span)?;
let lsb = get_imm(&ops[2], "bfi", instr.span)? as u32;
let width = get_imm(&ops[3], "bfi", instr.span)? as u32;
if lsb > 31 || width == 0 || width > 32 || lsb + width > 32 {
return Err(invalid_ops("bfi", "lsb/width out of range", instr.span));
}
let msb = lsb + width - 1;
((cond as u32) << 28)
| (0b0111110 << 21)
| (msb << 16)
| ((rd.arm_reg_num() as u32) << 12)
| (lsb << 7)
| (0b001 << 4)
| (rn.arm_reg_num() as u32)
}
"sbfx" => {
if ops.len() != 4 {
return Err(invalid_ops(
"sbfx",
"expected Rd, Rn, #lsb, #width",
instr.span,
));
}
let rd = get_arm_reg(&ops[0], "sbfx", instr.span)?;
let rn = get_arm_reg(&ops[1], "sbfx", instr.span)?;
let lsb = get_imm(&ops[2], "sbfx", instr.span)? as u32;
let width = get_imm(&ops[3], "sbfx", instr.span)? as u32;
if lsb > 31 || width == 0 || width > 32 || lsb + width > 32 {
return Err(invalid_ops("sbfx", "lsb/width out of range", instr.span));
}
let widthm1 = width - 1;
((cond as u32) << 28)
| (0b0111101 << 21)
| (widthm1 << 16)
| ((rd.arm_reg_num() as u32) << 12)
| (lsb << 7)
| (0b101 << 4)
| (rn.arm_reg_num() as u32)
}
"ubfx" => {
if ops.len() != 4 {
return Err(invalid_ops(
"ubfx",
"expected Rd, Rn, #lsb, #width",
instr.span,
));
}
let rd = get_arm_reg(&ops[0], "ubfx", instr.span)?;
let rn = get_arm_reg(&ops[1], "ubfx", instr.span)?;
let lsb = get_imm(&ops[2], "ubfx", instr.span)? as u32;
let width = get_imm(&ops[3], "ubfx", instr.span)? as u32;
if lsb > 31 || width == 0 || width > 32 || lsb + width > 32 {
return Err(invalid_ops("ubfx", "lsb/width out of range", instr.span));
}
let widthm1 = width - 1;
((cond as u32) << 28)
| (0b0111111 << 21)
| (widthm1 << 16)
| ((rd.arm_reg_num() as u32) << 12)
| (lsb << 7)
| (0b101 << 4)
| (rn.arm_reg_num() as u32)
}
_ => return Err(invalid_ops(mnemonic, "internal error", instr.span)),
};
emit32(buf, word);
Ok(())
}
fn encode_ldrex(
buf: &mut InstrBytes,
cond: Cond,
base: &str,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
let is_pair = base == "ldrexd";
let expected = if is_pair { 3 } else { 2 };
if ops.len() != expected {
return Err(invalid_ops(
base,
if is_pair {
"expected Rt, Rt2, [Rn]"
} else {
"expected Rd, [Rn]"
},
instr.span,
));
}
let rd = get_arm_reg(&ops[0], base, instr.span)?;
if is_pair {
let rt2 = get_arm_reg(&ops[1], base, instr.span)?;
if rd.arm_reg_num() % 2 != 0 {
return Err(invalid_ops(base, "Rt must be an even register", instr.span));
}
if rt2.arm_reg_num() != rd.arm_reg_num() + 1 {
return Err(invalid_ops(
base,
"Rt2 must be the register immediately after Rt",
instr.span,
));
}
}
let rn = match &ops[ops.len() - 1] {
Operand::Memory(m) => match m.base {
Some(r) if r.is_arm() => r,
_ => {
return Err(invalid_ops(
base,
"expected [Rn] memory operand",
instr.span,
))
}
},
_ => {
return Err(invalid_ops(
base,
"expected [Rn] memory operand",
instr.span,
))
}
};
let op_bits: u32 = match base {
"ldrex" => 0b00011001,
"ldrexb" => 0b00011101,
"ldrexh" => 0b00011111,
"ldrexd" => 0b00011011,
_ => return Err(invalid_ops(base, "unknown ldrex variant", instr.span)),
};
let word = ((cond as u32) << 28)
| (op_bits << 20)
| ((rn.arm_reg_num() as u32) << 16)
| ((rd.arm_reg_num() as u32) << 12)
| 0xF9F;
emit32(buf, word);
Ok(())
}
fn encode_strex(
buf: &mut InstrBytes,
cond: Cond,
base: &str,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
let is_pair = base == "strexd";
let expected = if is_pair { 4 } else { 3 };
if ops.len() != expected {
return Err(invalid_ops(
base,
if is_pair {
"expected Rd, Rt, Rt2, [Rn]"
} else {
"expected Rd, Rm, [Rn]"
},
instr.span,
));
}
let rd = get_arm_reg(&ops[0], base, instr.span)?;
let rm = get_arm_reg(&ops[1], base, instr.span)?;
if is_pair {
let rt2 = get_arm_reg(&ops[2], base, instr.span)?;
if rm.arm_reg_num() % 2 != 0 {
return Err(invalid_ops(base, "Rt must be an even register", instr.span));
}
if rt2.arm_reg_num() != rm.arm_reg_num() + 1 {
return Err(invalid_ops(
base,
"Rt2 must be the register immediately after Rt",
instr.span,
));
}
}
let rn = match &ops[ops.len() - 1] {
Operand::Memory(m) => match m.base {
Some(r) if r.is_arm() => r,
_ => {
return Err(invalid_ops(
base,
"expected [Rn] memory operand",
instr.span,
))
}
},
_ => {
return Err(invalid_ops(
base,
"expected [Rn] memory operand",
instr.span,
))
}
};
let op_bits: u32 = match base {
"strex" => 0b00011000,
"strexb" => 0b00011100,
"strexh" => 0b00011110,
"strexd" => 0b00011010,
_ => return Err(invalid_ops(base, "unknown strex variant", instr.span)),
};
let word = ((cond as u32) << 28)
| (op_bits << 20)
| ((rn.arm_reg_num() as u32) << 16)
| ((rd.arm_reg_num() as u32) << 12)
| (0xF << 8)
| (0b1001 << 4)
| (rm.arm_reg_num() as u32);
emit32(buf, word);
Ok(())
}
fn encode_barrier(
buf: &mut InstrBytes,
base: &str,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
let option = if ops.is_empty() {
0xF_u32
} else {
let v = get_imm(&ops[0], base, instr.span)? as u32;
if v > 0xF {
return Err(invalid_ops(base, "barrier option must be 0-15", instr.span));
}
v
};
let word = match base {
"dmb" => 0xF57F_F050 | option,
"dsb" => 0xF57F_F040 | option,
"isb" => 0xF57F_F060 | option,
_ => {
return Err(invalid_ops(
base,
&alloc::format!("unknown barrier instruction '{}'", base),
instr.span,
))
}
};
emit32(buf, word);
Ok(())
}
fn encode_extend(
buf: &mut InstrBytes,
cond: Cond,
mnemonic: &str,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
if ops.len() != 2 {
return Err(invalid_ops(mnemonic, "expected Rd, Rm", instr.span));
}
let rd = get_arm_reg(&ops[0], mnemonic, instr.span)?;
let rm = get_arm_reg(&ops[1], mnemonic, instr.span)?;
let op = match mnemonic {
"uxtb" => 0b01101110u32,
"uxth" => 0b01101111u32,
"sxtb" => 0b01101010u32,
"sxth" => 0b01101011u32,
_ => return Err(invalid_ops(mnemonic, "internal error", instr.span)),
};
let word = (((cond as u32) << 28)
| (op << 20)
| (0xF << 16) | ((rd.arm_reg_num() as u32) << 12)) | (0b0111 << 4)
| (rm.arm_reg_num() as u32);
emit32(buf, word);
Ok(())
}
fn encode_mrs(
buf: &mut InstrBytes,
cond: Cond,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
if ops.len() != 2 {
return Err(invalid_ops("mrs", "expected Rd, CPSR", instr.span));
}
let rd = get_arm_reg(&ops[0], "mrs", instr.span)?;
let r_bit = match &ops[1] {
Operand::PsrField(psr) => u32::from(psr.saved),
Operand::Register(Register::ArmCpsr) => 0,
_ => {
return Err(invalid_ops(
"mrs",
"expected CPSR or SPSR as source",
instr.span,
))
}
};
let word = ((cond as u32) << 28)
| (0b00010 << 23)
| (r_bit << 22)
| (0xF << 16)
| ((rd.arm_reg_num() as u32) << 12);
emit32(buf, word);
Ok(())
}
fn encode_msr(
buf: &mut InstrBytes,
cond: Cond,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
if ops.len() != 2 {
return Err(invalid_ops("msr", "expected CPSR, Rm_or_imm", instr.span));
}
let (r_bit, field_mask) = match &ops[0] {
Operand::PsrField(psr) => (u32::from(psr.saved), u32::from(psr.mask)),
Operand::Register(Register::ArmCpsr) => (0, 0b1001u32),
_ => {
return Err(invalid_ops(
"msr",
"expected CPSR/SPSR (optionally with a field selector) as destination",
instr.span,
))
}
};
match &ops[1] {
Operand::Register(rm) if rm.is_arm() => {
let word = ((cond as u32) << 28)
| (0b00010 << 23)
| (r_bit << 22)
| (0b10 << 20)
| (field_mask << 16)
| (0xF << 12)
| (rm.arm_reg_num() as u32);
emit32(buf, word);
}
Operand::Immediate(imm) => {
let val = *imm as u32;
let (imm8, rot) = encode_arm_imm(val).ok_or_else(|| {
invalid_ops(
"msr",
"immediate cannot be encoded as ARM rotated immediate",
instr.span,
)
})?;
let word = ((cond as u32) << 28)
| (0b00110 << 23)
| (r_bit << 22)
| (0b10 << 20)
| (field_mask << 16)
| (0xF << 12)
| ((rot as u32) << 8)
| (imm8 as u32);
emit32(buf, word);
}
_ => {
return Err(invalid_ops(
"msr",
"expected register or immediate",
instr.span,
))
}
}
Ok(())
}
fn emit16(buf: &mut InstrBytes, hw: u16) {
let b = hw.to_le_bytes();
buf.push(b[0]);
buf.push(b[1]);
}
fn require_lo(reg: Register, mnemonic: &str, span: crate::error::Span) -> Result<u8, AsmError> {
let n = reg.arm_reg_num();
if n > 7 {
return Err(invalid_ops(
mnemonic,
"register not accessible in 16-bit Thumb (must be R0-R7)",
span,
));
}
Ok(n)
}
fn encode_thumb(instr: &Instruction) -> Result<EncodedInstr, AsmError> {
let mut buf = InstrBytes::new();
let mut reloc: Option<Relocation> = None;
let mut relax: Option<crate::encoder::RelaxInfo> = None;
let mnemonic = instr.mnemonic.as_str();
let ops = &instr.operands;
let (base_with_s, cond) = parse_cond(mnemonic);
let (base, set_flags) = if base_with_s.ends_with('s')
&& base_with_s.len() > 1
&& matches!(
&base_with_s[..base_with_s.len() - 1],
"add"
| "sub"
| "mov"
| "and"
| "orr"
| "eor"
| "bic"
| "lsl"
| "lsr"
| "asr"
| "ror"
| "mul"
| "mvn"
| "adc"
| "sbc"
| "rsb"
| "neg"
| "tst"
| "cmn"
| "cmp"
) {
(&base_with_s[..base_with_s.len() - 1], true)
} else {
(base_with_s, false)
};
match base {
"nop" => {
emit16(&mut buf, 0xBF00);
}
"bkpt" => {
let imm = match ops.first() {
Some(Operand::Immediate(v)) => *v as u32,
_ => 0,
};
if imm > 255 {
return Err(invalid_ops("bkpt", "immediate must be 0-255", instr.span));
}
emit16(&mut buf, 0xBE00 | (imm as u16));
}
"svc" | "swi" => {
let imm = match ops.first() {
Some(Operand::Immediate(v)) => *v as u32,
_ => return Err(invalid_ops("svc", "expected immediate", instr.span)),
};
if imm > 255 {
return Err(invalid_ops("svc", "immediate must be 0-255", instr.span));
}
emit16(&mut buf, 0xDF00 | (imm as u16));
}
"bx" => {
let rm = match ops.first() {
Some(Operand::Register(r)) => r.arm_reg_num(),
_ => return Err(invalid_ops("bx", "expected register", instr.span)),
};
emit16(&mut buf, 0x4700 | ((rm as u16) << 3));
}
"blx" => {
let rm = match ops.first() {
Some(Operand::Register(r)) => r.arm_reg_num(),
_ => return Err(invalid_ops("blx", "expected register", instr.span)),
};
emit16(&mut buf, 0x4780 | ((rm as u16) << 3));
}
"b" => {
match ops.first() {
Some(Operand::Label(label_name)) => {
if !matches!(cond, Cond::Al) {
let cond_u16 = cond as u16;
let short_hw = 0xD000_u16 | (cond_u16 << 8);
let mut short_bytes = InstrBytes::new();
emit16(&mut short_bytes, short_hw);
let mut long_bytes = InstrBytes::new();
let hw1: u16 = 0xF000 | (cond_u16 << 6);
let hw2: u16 = 0x8000;
emit16(&mut long_bytes, hw1);
emit16(&mut long_bytes, hw2);
relax = Some(crate::encoder::RelaxInfo {
short_bytes,
short_reloc_offset: 0,
short_relocation: Some(Relocation {
offset: 0,
size: 2,
label: alloc::rc::Rc::from(label_name.as_str()),
kind: RelocKind::ThumbBranch8,
addend: 0,
trailing_bytes: 0,
}),
});
buf = long_bytes;
reloc = Some(Relocation {
offset: 0,
size: 4,
label: alloc::rc::Rc::from(label_name.as_str()),
kind: RelocKind::ThumbCondBranchW,
addend: 0,
trailing_bytes: 0,
});
} else {
let mut short_bytes = InstrBytes::new();
emit16(&mut short_bytes, 0xE000);
let mut long_bytes = InstrBytes::new();
emit16(&mut long_bytes, 0xF000);
emit16(&mut long_bytes, 0x9000);
relax = Some(crate::encoder::RelaxInfo {
short_bytes,
short_reloc_offset: 0,
short_relocation: Some(Relocation {
offset: 0,
size: 2,
label: alloc::rc::Rc::from(label_name.as_str()),
kind: RelocKind::ThumbBranch11,
addend: 0,
trailing_bytes: 0,
}),
});
buf = long_bytes;
reloc = Some(Relocation {
offset: 0,
size: 4,
label: alloc::rc::Rc::from(label_name.as_str()),
kind: RelocKind::ThumbBranchW,
addend: 0,
trailing_bytes: 0,
});
}
}
Some(Operand::Immediate(off)) => {
let off = *off;
if !matches!(cond, Cond::Al) {
let delta = (off - 4) >> 1;
if !(-128..=127).contains(&delta) {
let cond_u16 = cond as u16;
let encoded = encode_thumb2_cond_branch(delta as i32, cond_u16);
emit16(&mut buf, encoded.0);
emit16(&mut buf, encoded.1);
} else {
let imm8 = (delta as i8) as u8;
let cond_u16 = cond as u16;
emit16(&mut buf, 0xD000 | (cond_u16 << 8) | (imm8 as u16));
}
} else {
let delta = (off - 4) >> 1;
if !(-1024..=1023).contains(&delta) {
let encoded = encode_thumb2_branch(delta as i32);
emit16(&mut buf, encoded.0);
emit16(&mut buf, encoded.1);
} else {
let imm11 = (delta as u16) & 0x7FF;
emit16(&mut buf, 0xE000 | imm11);
}
}
}
_ => {
return Err(invalid_ops("b", "expected label or offset", instr.span));
}
}
}
"bl" => match ops.first() {
Some(Operand::Label(label_name)) => {
emit16(&mut buf, 0xF000);
emit16(&mut buf, 0xD000);
reloc = Some(Relocation {
offset: 0,
size: 4,
label: alloc::rc::Rc::from(label_name.as_str()),
kind: RelocKind::ThumbBl,
addend: 0,
trailing_bytes: 0,
});
}
Some(Operand::Immediate(off)) => {
let delta = ((*off - 4) >> 1) as i32;
let encoded = encode_thumb2_bl(delta);
emit16(&mut buf, encoded.0);
emit16(&mut buf, encoded.1);
}
_ => {
return Err(invalid_ops("bl", "expected label or offset", instr.span));
}
},
"push" => {
match ops.first() {
Some(Operand::RegisterList(regs)) => {
let mut mask: u16 = 0;
for reg in regs {
let n = reg.arm_reg_num();
if n == 14 {
mask |= 1 << 8;
} else if n <= 7 {
mask |= 1 << n;
} else {
return Err(invalid_ops(
"push",
"only R0-R7 and LR allowed in 16-bit Thumb PUSH",
instr.span,
));
}
}
emit16(&mut buf, 0xB400 | mask);
}
_ => {
return Err(invalid_ops("push", "expected register list", instr.span));
}
}
}
"pop" => {
match ops.first() {
Some(Operand::RegisterList(regs)) => {
let mut mask: u16 = 0;
for reg in regs {
let n = reg.arm_reg_num();
if n == 15 {
mask |= 1 << 8;
} else if n <= 7 {
mask |= 1 << n;
} else {
return Err(invalid_ops(
"pop",
"only R0-R7 and PC allowed in 16-bit Thumb POP",
instr.span,
));
}
}
emit16(&mut buf, 0xBC00 | mask);
}
_ => {
return Err(invalid_ops("pop", "expected register list", instr.span));
}
}
}
"mov" | "movs" if !set_flags => {
thumb_encode_mov(&mut buf, ops, instr, base == "movs" || set_flags)?;
}
"mov" if set_flags => {
thumb_encode_mov(&mut buf, ops, instr, true)?;
}
"cmp" => {
match (ops.first(), ops.get(1)) {
(Some(Operand::Register(rn)), Some(Operand::Immediate(imm))) => {
let n = rn.arm_reg_num();
if n > 7 {
return Err(invalid_ops("cmp", "CMP imm8 requires R0-R7", instr.span));
}
let imm = *imm;
if !(0..=255).contains(&imm) {
return Err(invalid_ops("cmp", "immediate must be 0-255", instr.span));
}
emit16(&mut buf, 0x2800 | ((n as u16) << 8) | (imm as u16));
}
(Some(Operand::Register(rn)), Some(Operand::Register(rm))) => {
let n = rn.arm_reg_num();
let m = rm.arm_reg_num();
if n <= 7 && m <= 7 {
emit16(&mut buf, 0x4280 | ((m as u16) << 3) | (n as u16));
} else {
let n_hi = (n >> 3) & 1;
let n_lo = n & 0x7;
emit16(
&mut buf,
0x4500 | ((n_hi as u16) << 7) | ((m as u16) << 3) | (n_lo as u16),
);
}
}
_ => {
return Err(invalid_ops(
"cmp",
"expected register, immediate or register",
instr.span,
))
}
}
}
"tst" => {
match (ops.first(), ops.get(1)) {
(Some(Operand::Register(rn)), Some(Operand::Register(rm))) => {
let n = require_lo(*rn, "tst", instr.span)?;
let m = require_lo(*rm, "tst", instr.span)?;
emit16(&mut buf, 0x4200 | ((m as u16) << 3) | (n as u16));
}
_ => return Err(invalid_ops("tst", "expected two registers", instr.span)),
}
}
"cmn" => {
match (ops.first(), ops.get(1)) {
(Some(Operand::Register(rn)), Some(Operand::Register(rm))) => {
let n = require_lo(*rn, "cmn", instr.span)?;
let m = require_lo(*rm, "cmn", instr.span)?;
emit16(&mut buf, 0x42C0 | ((m as u16) << 3) | (n as u16));
}
_ => return Err(invalid_ops("cmn", "expected two registers", instr.span)),
}
}
"add" | "adds" if !set_flags => {
thumb_encode_add(&mut buf, ops, instr, base == "adds", &mut reloc)?;
}
"add" if set_flags => {
thumb_encode_add(&mut buf, ops, instr, true, &mut reloc)?;
}
"sub" | "subs" if !set_flags => {
thumb_encode_sub(&mut buf, ops, instr, base == "subs")?;
}
"sub" if set_flags => {
thumb_encode_sub(&mut buf, ops, instr, true)?;
}
"and" | "orr" | "eor" | "bic" | "mvn" | "neg" | "rsb" | "adc" | "sbc" | "mul" | "ror" => {
thumb_encode_alu_reg(&mut buf, base, ops, instr)?;
}
"lsl" | "lsr" | "asr" => {
thumb_encode_shift(&mut buf, base, ops, instr)?;
}
"ldr" => thumb_encode_ldr_str(&mut buf, "ldr", ops, instr, &mut reloc)?,
"str" => thumb_encode_ldr_str(&mut buf, "str", ops, instr, &mut reloc)?,
"ldrb" => thumb_encode_ldr_str(&mut buf, "ldrb", ops, instr, &mut reloc)?,
"strb" => thumb_encode_ldr_str(&mut buf, "strb", ops, instr, &mut reloc)?,
"ldrh" => thumb_encode_ldr_str(&mut buf, "ldrh", ops, instr, &mut reloc)?,
"strh" => thumb_encode_ldr_str(&mut buf, "strh", ops, instr, &mut reloc)?,
"ldrsb" => thumb_encode_ldr_str_reg_only(&mut buf, "ldrsb", ops, instr)?,
"ldrsh" => thumb_encode_ldr_str_reg_only(&mut buf, "ldrsh", ops, instr)?,
"add.w" | "adds.w" | "sub.w" | "subs.w" | "mov.w" | "movs.w" | "and.w" | "orr.w"
| "eor.w" | "bic.w" | "mvn.w" | "lsl.w" | "lsr.w" | "asr.w" | "ror.w" => {
thumb_encode_wide_dp(&mut buf, base, ops, instr)?;
}
"b.w" => {
match ops.first() {
Some(Operand::Label(label_name)) => {
emit16(&mut buf, 0xF000);
emit16(&mut buf, 0x9000);
reloc = Some(Relocation {
offset: 0,
size: 4,
label: alloc::rc::Rc::from(label_name.as_str()),
kind: RelocKind::ThumbBranchW,
addend: 0,
trailing_bytes: 0,
});
}
_ => return Err(invalid_ops("b.w", "expected label", instr.span)),
}
}
"it" | "ite" | "itt" | "itte" | "itet" | "itee" | "ittt" | "iteet" | "ittte" | "ittet"
| "itett" | "ittee" | "itete" | "iteee" | "itttt" => {
thumb_encode_it(&mut buf, mnemonic, ops, instr)?;
}
_ => {
return Err(AsmError::UnknownMnemonic {
mnemonic: String::from(mnemonic),
arch: crate::error::ArchName::Thumb,
span: instr.span,
});
}
}
Ok(EncodedInstr {
bytes: buf,
relocation: reloc,
relax,
})
}
fn thumb_encode_mov(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
set_flags: bool,
) -> Result<(), AsmError> {
match (ops.first(), ops.get(1)) {
(Some(Operand::Register(rd)), Some(Operand::Immediate(imm))) => {
let d = require_lo(*rd, "mov", instr.span)?;
let imm = *imm;
if !(0..=255).contains(&imm) {
return Err(invalid_ops(
"mov",
"immediate must be 0-255 for Thumb MOV",
instr.span,
));
}
emit16(buf, 0x2000 | ((d as u16) << 8) | (imm as u16));
}
(Some(Operand::Register(rd)), Some(Operand::Register(rs))) => {
let d = rd.arm_reg_num();
let s = rs.arm_reg_num();
if d <= 7 && s <= 7 && set_flags {
emit16(buf, ((s as u16) << 3) | (d as u16));
} else {
let d_hi = (d >> 3) & 1;
let d_lo = d & 0x7;
emit16(
buf,
0x4600 | ((d_hi as u16) << 7) | ((s as u16) << 3) | (d_lo as u16),
);
}
}
_ => {
return Err(invalid_ops(
"mov",
"expected register and immediate or register",
instr.span,
))
}
}
Ok(())
}
fn thumb_encode_add(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
set_flags: bool,
_reloc: &mut Option<Relocation>,
) -> Result<(), AsmError> {
match ops.len() {
2 => {
match (ops.first(), ops.get(1)) {
(Some(Operand::Register(rd)), Some(Operand::Immediate(imm))) => {
let d = require_lo(*rd, "add", instr.span)?;
let imm = *imm;
if !(0..=255).contains(&imm) {
return Err(invalid_ops("add", "immediate must be 0-255", instr.span));
}
emit16(buf, 0x3000 | ((d as u16) << 8) | (imm as u16));
}
(Some(Operand::Register(rd)), Some(Operand::Register(rm))) => {
let d = rd.arm_reg_num();
let m = rm.arm_reg_num();
if d <= 7 && m <= 7 && set_flags {
emit16(
buf,
0x1800 | ((m as u16) << 6) | ((d as u16) << 3) | (d as u16),
);
} else {
let d_hi = (d >> 3) & 1;
let d_lo = d & 0x7;
emit16(
buf,
0x4400 | ((d_hi as u16) << 7) | ((m as u16) << 3) | (d_lo as u16),
);
}
}
_ => {
return Err(invalid_ops(
"add",
"expected register and immediate or register",
instr.span,
))
}
}
}
3 => {
match (&ops[0], &ops[1], &ops[2]) {
(Operand::Register(rd), Operand::Register(rn), Operand::Immediate(imm)) => {
let d = require_lo(*rd, "add", instr.span)?;
let n = require_lo(*rn, "add", instr.span)?;
let imm = *imm;
if (0..=7).contains(&imm) {
emit16(
buf,
0x1C00 | ((imm as u16) << 6) | ((n as u16) << 3) | (d as u16),
);
} else if (0..=255).contains(&imm) && d == n {
emit16(buf, 0x3000 | ((d as u16) << 8) | (imm as u16));
} else {
return Err(invalid_ops(
"add",
"immediate too large for 16-bit Thumb ADD",
instr.span,
));
}
}
(Operand::Register(rd), Operand::Register(rn), Operand::Register(rm)) => {
let d = require_lo(*rd, "add", instr.span)?;
let n = require_lo(*rn, "add", instr.span)?;
let m = require_lo(*rm, "add", instr.span)?;
emit16(
buf,
0x1800 | ((m as u16) << 6) | ((n as u16) << 3) | (d as u16),
);
}
(Operand::Register(rd), Operand::Register(rn), _)
if rd.arm_reg_num() == 13 || rn.arm_reg_num() == 13 =>
{
return Err(invalid_ops(
"add",
"SP arithmetic not yet supported",
instr.span,
));
}
_ => {
return Err(invalid_ops(
"add",
"invalid operands for Thumb ADD",
instr.span,
))
}
}
}
_ => return Err(invalid_ops("add", "expected 2 or 3 operands", instr.span)),
}
Ok(())
}
fn thumb_encode_sub(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
_set_flags: bool,
) -> Result<(), AsmError> {
match ops.len() {
2 => {
match (ops.first(), ops.get(1)) {
(Some(Operand::Register(rd)), Some(Operand::Immediate(imm))) => {
let d = require_lo(*rd, "sub", instr.span)?;
let imm = *imm;
if !(0..=255).contains(&imm) {
return Err(invalid_ops("sub", "immediate must be 0-255", instr.span));
}
emit16(buf, 0x3800 | ((d as u16) << 8) | (imm as u16));
}
_ => {
return Err(invalid_ops(
"sub",
"expected register and immediate",
instr.span,
))
}
}
}
3 => {
match (&ops[0], &ops[1], &ops[2]) {
(Operand::Register(rd), Operand::Register(rn), Operand::Immediate(imm)) => {
let d = require_lo(*rd, "sub", instr.span)?;
let n = require_lo(*rn, "sub", instr.span)?;
let imm = *imm;
if (0..=7).contains(&imm) {
emit16(
buf,
0x1E00 | ((imm as u16) << 6) | ((n as u16) << 3) | (d as u16),
);
} else if (0..=255).contains(&imm) && d == n {
emit16(buf, 0x3800 | ((d as u16) << 8) | (imm as u16));
} else {
return Err(invalid_ops(
"sub",
"immediate too large for 16-bit Thumb SUB",
instr.span,
));
}
}
(Operand::Register(rd), Operand::Register(rn), Operand::Register(rm)) => {
let d = require_lo(*rd, "sub", instr.span)?;
let n = require_lo(*rn, "sub", instr.span)?;
let m = require_lo(*rm, "sub", instr.span)?;
emit16(
buf,
0x1A00 | ((m as u16) << 6) | ((n as u16) << 3) | (d as u16),
);
}
_ => return Err(invalid_ops("sub", "invalid operands", instr.span)),
}
}
_ => return Err(invalid_ops("sub", "expected 2 or 3 operands", instr.span)),
}
Ok(())
}
fn thumb_encode_alu_reg(
buf: &mut InstrBytes,
base: &str,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
let (rd, rm) = match (ops.first(), ops.get(1)) {
(Some(Operand::Register(r1)), Some(Operand::Register(r2))) => (
require_lo(*r1, base, instr.span)?,
require_lo(*r2, base, instr.span)?,
),
_ => return Err(invalid_ops(base, "expected two low registers", instr.span)),
};
let opcode: u16 = match base {
"and" => 0x4000,
"eor" => 0x4040,
"adc" => 0x4140,
"sbc" => 0x4180,
"ror" => 0x41C0,
"neg" | "rsb" => 0x4240, "tst" => 0x4200,
"cmn" => 0x42C0,
"orr" => 0x4300,
"mul" => 0x4340,
"bic" => 0x4380,
"mvn" => 0x43C0,
_ => return Err(invalid_ops(base, "unknown ALU op", instr.span)),
};
emit16(buf, opcode | ((rm as u16) << 3) | (rd as u16));
Ok(())
}
fn thumb_encode_shift(
buf: &mut InstrBytes,
base: &str,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
match (ops.first(), ops.get(1), ops.get(2)) {
(
Some(Operand::Register(rd)),
Some(Operand::Register(rm)),
Some(Operand::Immediate(imm)),
) => {
let d = require_lo(*rd, base, instr.span)?;
let m = require_lo(*rm, base, instr.span)?;
let imm = *imm as u16;
if imm > 31 {
return Err(invalid_ops(base, "shift amount must be 0-31", instr.span));
}
let opcode = match base {
"lsl" => 0x0000,
"lsr" => 0x0800,
"asr" => 0x1000,
_ => return Err(invalid_ops(base, "unhandled mnemonic", instr.span)),
};
emit16(buf, opcode | (imm << 6) | ((m as u16) << 3) | (d as u16));
}
(Some(Operand::Register(rd)), Some(Operand::Register(rs)), None) => {
let d = require_lo(*rd, base, instr.span)?;
let s = require_lo(*rs, base, instr.span)?;
let opcode = match base {
"lsl" => 0x4080,
"lsr" => 0x40C0,
"asr" => 0x4100,
_ => return Err(invalid_ops(base, "unhandled mnemonic", instr.span)),
};
emit16(buf, opcode | ((s as u16) << 3) | (d as u16));
}
_ => return Err(invalid_ops(base, "expected 2 or 3 operands", instr.span)),
}
Ok(())
}
fn thumb_encode_ldr_str(
buf: &mut InstrBytes,
mnemonic: &str,
ops: &OperandList,
instr: &Instruction,
reloc: &mut Option<Relocation>,
) -> Result<(), AsmError> {
let is_load = mnemonic.starts_with("ldr");
let is_byte = mnemonic.ends_with('b');
let is_half = mnemonic.ends_with('h');
match (ops.first(), ops.get(1)) {
(Some(Operand::Register(rt)), Some(Operand::Memory(mem))) => {
let t = require_lo(*rt, mnemonic, instr.span)?;
let base_reg = match mem.base {
Some(r) => r.arm_reg_num(),
None => {
return Err(invalid_ops(
mnemonic,
"memory operand requires base register",
instr.span,
))
}
};
if let Some(idx_reg) = mem.index {
let m = require_lo(idx_reg, mnemonic, instr.span)?;
let n = require_lo(
Register::from_arm_num(base_reg).ok_or_else(|| {
invalid_ops(mnemonic, "invalid base register number", instr.span)
})?,
mnemonic,
instr.span,
)?;
let opcode = if is_byte {
if is_load {
0x5C00_u16
} else {
0x5400
}
} else if is_half {
if is_load {
0x5A00_u16
} else {
0x5200
}
} else if is_load {
0x5800_u16
} else {
0x5000
};
emit16(
buf,
opcode | ((m as u16) << 6) | ((n as u16) << 3) | (t as u16),
);
} else {
let off = mem.disp;
let n = base_reg;
if n == 13 {
if is_byte || is_half {
return Err(invalid_ops(
mnemonic,
"LDRB/STRB/LDRH/STRH not available with SP-relative in Thumb",
instr.span,
));
}
if !(0..=1020).contains(&off) || (off & 3) != 0 {
return Err(invalid_ops(
mnemonic,
"SP-relative offset must be 0-1020 and word-aligned",
instr.span,
));
}
let imm8 = (off >> 2) as u16;
if is_load {
emit16(buf, 0x9800 | ((t as u16) << 8) | imm8);
} else {
emit16(buf, 0x9000 | ((t as u16) << 8) | imm8);
}
} else {
let n = require_lo(
Register::from_arm_num(n).ok_or_else(|| {
invalid_ops(mnemonic, "invalid base register number", instr.span)
})?,
mnemonic,
instr.span,
)?;
if is_byte {
if !(0..=31).contains(&off) {
return Err(invalid_ops(
mnemonic,
"byte offset must be 0-31",
instr.span,
));
}
let imm5 = off as u16;
let base_op = if is_load { 0x7800_u16 } else { 0x7000 };
emit16(buf, base_op | (imm5 << 6) | ((n as u16) << 3) | (t as u16));
} else if is_half {
if !(0..=62).contains(&off) || (off & 1) != 0 {
return Err(invalid_ops(
mnemonic,
"halfword offset must be 0-62 and half-aligned",
instr.span,
));
}
let imm5 = (off >> 1) as u16;
let base_op = if is_load { 0x8800_u16 } else { 0x8000 };
emit16(buf, base_op | (imm5 << 6) | ((n as u16) << 3) | (t as u16));
} else {
if !(0..=124).contains(&off) || (off & 3) != 0 {
return Err(invalid_ops(
mnemonic,
"word offset must be 0-124 and word-aligned",
instr.span,
));
}
let imm5 = (off >> 2) as u16;
let base_op = if is_load { 0x6800_u16 } else { 0x6000 };
emit16(buf, base_op | (imm5 << 6) | ((n as u16) << 3) | (t as u16));
}
}
}
}
(Some(Operand::Register(rt)), Some(Operand::Label(label))) if is_load => {
let t = require_lo(*rt, mnemonic, instr.span)?;
let hw = 0x4800_u16 | ((t as u16) << 8);
let reloc_offset = buf.len();
emit16(buf, hw);
*reloc = Some(Relocation {
offset: reloc_offset,
size: 2,
label: alloc::rc::Rc::from(&**label),
kind: RelocKind::ThumbLdrLit8,
addend: 0,
trailing_bytes: 0,
});
return Ok(());
}
_ => {
return Err(invalid_ops(
mnemonic,
"expected Rt, [Rn, #offset] or Rt, [Rn, Rm]",
instr.span,
));
}
}
let _ = reloc;
Ok(())
}
fn thumb_encode_ldr_str_reg_only(
buf: &mut InstrBytes,
mnemonic: &str,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
match (ops.first(), ops.get(1)) {
(Some(Operand::Register(rt)), Some(Operand::Memory(mem))) => {
let t = require_lo(*rt, mnemonic, instr.span)?;
let base_reg = match mem.base {
Some(r) => r.arm_reg_num(),
None => return Err(invalid_ops(mnemonic, "requires base register", instr.span)),
};
let idx_reg = match mem.index {
Some(r) => r,
None => {
return Err(invalid_ops(
mnemonic,
"register offset only in Thumb",
instr.span,
))
}
};
let n = require_lo(
Register::from_arm_num(base_reg).ok_or_else(|| {
invalid_ops(mnemonic, "invalid base register number", instr.span)
})?,
mnemonic,
instr.span,
)?;
let m = require_lo(idx_reg, mnemonic, instr.span)?;
let opcode = match mnemonic {
"ldrsb" => 0x5600_u16,
"ldrsh" => 0x5E00_u16,
_ => return Err(invalid_ops(mnemonic, "unhandled mnemonic", instr.span)),
};
emit16(
buf,
opcode | ((m as u16) << 6) | ((n as u16) << 3) | (t as u16),
);
}
_ => return Err(invalid_ops(mnemonic, "expected Rt, [Rn, Rm]", instr.span)),
}
Ok(())
}
fn thumb_encode_wide_dp(
buf: &mut InstrBytes,
base: &str,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
let mnemonic = base.trim_end_matches(".w");
let (mnemonic, _set_flags) = if mnemonic.ends_with('s') && mnemonic.len() > 1 {
(&mnemonic[..mnemonic.len() - 1], true)
} else {
(mnemonic, false)
};
match (ops.first(), ops.get(1), ops.get(2)) {
(Some(Operand::Register(rd)), Some(Operand::Register(rn)), Some(Operand::Register(rm))) => {
let d = rd.arm_reg_num();
let n = rn.arm_reg_num();
let m = rm.arm_reg_num();
let (opc, s_bit) = match mnemonic {
"and" => (0b0000_u16, 0_u16),
"eor" => (0b0100, 0),
"add" => (0b1000, 0),
"adc" => (0b1010, 0),
"sbc" => (0b1011, 0),
"sub" => (0b1101, 0),
"orr" => (0b0010, 0),
"bic" => (0b0001, 0),
"mvn" => (0b0011, 0), _ => {
return Err(invalid_ops(base, "unsupported wide operation", instr.span));
}
};
let _hw1: u16 = 0xEA00 | (s_bit << 4) | (opc << 5 >> 1) | ((n as u16) & 0xF);
let hw1: u16 = 0xEA00 | ((opc & 0xE) << 4) | ((opc & 1) << 4) | (n as u16 & 0xF);
let hw2: u16 = ((d as u16 & 0xF) << 8) | (m as u16 & 0xF);
emit16(buf, hw1);
emit16(buf, hw2);
}
(
Some(Operand::Register(rd)),
Some(Operand::Register(rn)),
Some(Operand::Immediate(imm)),
) => {
let d = rd.arm_reg_num();
let n = rn.arm_reg_num();
let imm = *imm as u32;
let encoded = encode_thumb2_modified_imm(imm).ok_or_else(|| {
invalid_ops(
base,
"immediate not encodable in Thumb-2 modified immediate",
instr.span,
)
})?;
let opc = match mnemonic {
"add" => 0b1000_u16,
"sub" => 0b1101,
"and" => 0b0000,
"orr" => 0b0010,
"eor" => 0b0100,
"bic" => 0b0001,
_ => {
return Err(invalid_ops(
base,
"unsupported wide immediate operation",
instr.span,
))
}
};
let i_bit = (encoded >> 11) & 1;
let imm3 = (encoded >> 8) & 0x7;
let imm8 = encoded & 0xFF;
let s_bit = if _set_flags { 1_u16 } else { 0 };
let hw1: u16 =
0xF000 | (i_bit << 10) | ((opc >> 1) << 5) | (s_bit << 4) | (n as u16 & 0xF);
let hw2: u16 = (imm3 << 12) | ((u16::from(d)) << 8) | imm8;
emit16(buf, hw1);
emit16(buf, hw2);
}
_ => {
return Err(invalid_ops(
base,
"invalid operands for wide instruction",
instr.span,
))
}
}
Ok(())
}
fn thumb_encode_it(
buf: &mut InstrBytes,
mnemonic: &str,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
let cond_str = match ops.first() {
Some(Operand::Label(name)) => name.as_str(),
Some(Operand::Register(_)) => {
return Err(invalid_ops(
mnemonic,
"expected condition code (eq, ne, cs, etc.)",
instr.span,
));
}
_ => return Err(invalid_ops(mnemonic, "expected condition code", instr.span)),
};
let firstcond = match cond_str {
"eq" => 0x0_u8,
"ne" => 0x1,
"cs" | "hs" => 0x2,
"cc" | "lo" => 0x3,
"mi" => 0x4,
"pl" => 0x5,
"vs" => 0x6,
"vc" => 0x7,
"hi" => 0x8,
"ls" => 0x9,
"ge" => 0xA,
"lt" => 0xB,
"gt" => 0xC,
"le" => 0xD,
"al" => 0xE,
_ => return Err(invalid_ops(mnemonic, "unknown condition code", instr.span)),
};
let suffix = &mnemonic[2..]; let fc_bit0 = firstcond & 1;
let mask: u8 = match suffix.len() {
0 => {
0b1000
}
1 => {
let bit = if suffix.as_bytes()[0] == b't' {
fc_bit0
} else {
fc_bit0 ^ 1
};
(bit << 3) | 0b0100
}
2 => {
let b1 = if suffix.as_bytes()[0] == b't' {
fc_bit0
} else {
fc_bit0 ^ 1
};
let b2 = if suffix.as_bytes()[1] == b't' {
fc_bit0
} else {
fc_bit0 ^ 1
};
(b1 << 3) | (b2 << 2) | 0b0010
}
3 => {
let b1 = if suffix.as_bytes()[0] == b't' {
fc_bit0
} else {
fc_bit0 ^ 1
};
let b2 = if suffix.as_bytes()[1] == b't' {
fc_bit0
} else {
fc_bit0 ^ 1
};
let b3 = if suffix.as_bytes()[2] == b't' {
fc_bit0
} else {
fc_bit0 ^ 1
};
(b1 << 3) | (b2 << 2) | (b3 << 1) | 0b0001
}
_ => {
return Err(invalid_ops(
mnemonic,
"IT block too long (max 4 instructions)",
instr.span,
))
}
};
emit16(buf, 0xBF00 | ((firstcond as u16) << 4) | (mask as u16));
Ok(())
}
fn encode_thumb2_bl(offset: i32) -> (u16, u16) {
let s = if offset < 0 { 1_u16 } else { 0 };
let imm = offset as u32;
let imm10 = (imm >> 11) & 0x3FF;
let imm11 = imm & 0x7FF;
let j1 = ((!(imm >> 23) ^ s as u32) & 1) as u16; let j2 = ((!(imm >> 22) ^ s as u32) & 1) as u16;
let hw1 = 0xF000 | (s << 10) | (imm10 as u16);
let hw2 = 0xD000 | (j1 << 13) | (j2 << 11) | (imm11 as u16);
(hw1, hw2)
}
fn encode_thumb2_branch(offset: i32) -> (u16, u16) {
let s = if offset < 0 { 1_u16 } else { 0 };
let imm = offset as u32;
let imm10 = (imm >> 11) & 0x3FF;
let imm11 = imm & 0x7FF;
let j1 = ((!(imm >> 23) ^ s as u32) & 1) as u16;
let j2 = ((!(imm >> 22) ^ s as u32) & 1) as u16;
let hw1 = 0xF000 | (s << 10) | (imm10 as u16);
let hw2 = 0x9000 | (j1 << 13) | (j2 << 11) | (imm11 as u16);
(hw1, hw2)
}
fn encode_thumb2_cond_branch(offset: i32, cond: u16) -> (u16, u16) {
let s = if offset < 0 { 1_u16 } else { 0 };
let imm = offset as u32;
let imm6 = (imm >> 11) & 0x3F;
let imm11 = imm & 0x7FF;
let j1 = ((imm >> 17) & 1) as u16;
let j2 = ((imm >> 18) & 1) as u16;
let hw1 = 0xF000 | (s << 10) | ((cond & 0xF) << 6) | (imm6 as u16);
let hw2 = 0x8000 | (j1 << 13) | (j2 << 11) | (imm11 as u16);
(hw1, hw2)
}
fn encode_thumb2_modified_imm(val: u32) -> Option<u16> {
if val <= 255 {
return Some(val as u16);
}
if (val >> 16) == (val & 0xFFFF) && (val & 0xFF00) == 0 {
return Some(0x100 | (val & 0xFF) as u16);
}
if (val >> 16) == (val & 0xFFFF) && (val & 0xFF) == 0 {
return Some(0x200 | ((val >> 8) & 0xFF) as u16);
}
if (val >> 24) == ((val >> 16) & 0xFF)
&& (val >> 24) == ((val >> 8) & 0xFF)
&& (val >> 24) == (val & 0xFF)
{
return Some(0x300 | (val & 0xFF) as u16);
}
for rot in 8..=31_u32 {
let unrotated = val.rotate_left(rot);
if unrotated <= 255 && (unrotated & 0x80) != 0 {
return Some(((rot as u16) << 7) | (unrotated as u16 & 0x7F));
}
}
None
}
impl Register {
fn from_arm_num(n: u8) -> Option<Register> {
use Register::*;
match n {
0 => Some(ArmR0),
1 => Some(ArmR1),
2 => Some(ArmR2),
3 => Some(ArmR3),
4 => Some(ArmR4),
5 => Some(ArmR5),
6 => Some(ArmR6),
7 => Some(ArmR7),
8 => Some(ArmR8),
9 => Some(ArmR9),
10 => Some(ArmR10),
11 => Some(ArmR11),
12 => Some(ArmR12),
13 => Some(ArmSp),
14 => Some(ArmLr),
15 => Some(ArmPc),
_ => None,
}
}
}
pub fn encode_arm(instr: &Instruction, arch: Arch) -> Result<EncodedInstr, AsmError> {
if arch == Arch::Thumb {
return encode_thumb(instr);
}
let mut buf = InstrBytes::new();
let mut reloc: Option<Relocation> = None;
let mnemonic = instr.mnemonic.as_str();
let ops = &instr.operands;
let (base_with_s, cond) = parse_cond(mnemonic);
let (base, set_flags) = if base_with_s.ends_with('s')
&& base_with_s.len() > 1
&& (dp_opcode(&base_with_s[..base_with_s.len() - 1]).is_some()
|| matches!(
&base_with_s[..base_with_s.len() - 1],
"mul" | "mla" | "umull" | "smull" | "umlal" | "smlal"
)) {
(&base_with_s[..base_with_s.len() - 1], true)
} else {
(base_with_s, false)
};
if dp_opcode(base).is_some() {
encode_dp(&mut buf, cond, base, ops, instr, set_flags)?;
} else {
match base {
"nop" => encode_nop(&mut buf, cond),
"bkpt" => encode_bkpt(&mut buf, ops, instr)?,
"b" | "bl" => encode_branch(&mut buf, cond, base, ops, instr, &mut reloc)?,
"bx" => encode_bx(&mut buf, cond, false, ops, instr)?,
"blx" => encode_bx(&mut buf, cond, true, ops, instr)?,
"ldr" | "str" | "ldrb" | "strb" => {
encode_ldr_str(&mut buf, cond, base, ops, instr, &mut reloc)?
}
"ldrh" | "strh" | "ldrsb" | "ldrsh" => {
encode_ldr_str_h(&mut buf, cond, base, ops, instr)?
}
"push" => encode_push_pop(&mut buf, cond, "push", ops, instr)?,
"pop" => encode_push_pop(&mut buf, cond, "pop", ops, instr)?,
"mul" | "mla" => encode_mul(&mut buf, cond, base, ops, instr, set_flags)?,
"umull" | "smull" | "umlal" | "smlal" => {
encode_long_mul(&mut buf, cond, base, ops, instr, set_flags)?
}
"lsl" | "lsr" | "asr" | "ror" | "rrx" => {
encode_shift_alias(&mut buf, cond, base, ops, instr, set_flags)?
}
"movw" => encode_movw_movt(&mut buf, cond, "movw", ops, instr)?,
"movt" => encode_movw_movt(&mut buf, cond, "movt", ops, instr)?,
"svc" | "swi" => encode_svc(&mut buf, cond, ops, instr)?,
"adr" => encode_adr(&mut buf, cond, ops, instr, &mut reloc)?,
"clz" => encode_clz(&mut buf, cond, ops, instr)?,
"rev" | "rev16" | "revsh" | "rbit" => encode_rev(&mut buf, cond, base, ops, instr)?,
"bfc" | "bfi" | "sbfx" | "ubfx" => encode_bitfield(&mut buf, cond, base, ops, instr)?,
"uxtb" | "uxth" | "sxtb" | "sxth" => encode_extend(&mut buf, cond, base, ops, instr)?,
"mrs" => encode_mrs(&mut buf, cond, ops, instr)?,
"msr" => encode_msr(&mut buf, cond, ops, instr)?,
"ldrex" | "ldrexb" | "ldrexh" | "ldrexd" => {
encode_ldrex(&mut buf, cond, base, ops, instr)?
}
"strex" | "strexb" | "strexh" | "strexd" => {
encode_strex(&mut buf, cond, base, ops, instr)?
}
"dmb" | "dsb" | "isb" => encode_barrier(&mut buf, base, ops, instr)?,
"ldm" | "ldmia" | "ldmfd" | "ldmdb" | "ldmea" | "ldmib" | "ldmed" | "ldmda"
| "ldmfa" | "stm" | "stmia" | "stmea" | "stmdb" | "stmfd" | "stmib" | "stmfa"
| "stmda" | "stmed" => encode_ldm_stm(&mut buf, cond, base, ops, instr)?,
_ => {
return Err(AsmError::UnknownMnemonic {
mnemonic: String::from(mnemonic),
arch: crate::error::ArchName::Arm,
span: instr.span,
});
}
}
}
Ok(EncodedInstr {
bytes: buf,
relocation: reloc,
relax: None,
})
}
#[cfg(test)]
mod tests {
use crate::assemble;
use crate::ir::Arch;
fn arm(src: &str) -> u32 {
let bytes = assemble(src, Arch::Arm).unwrap();
assert_eq!(bytes.len(), 4, "ARM instruction must be 4 bytes: {src}");
u32::from_le_bytes(bytes[..4].try_into().unwrap())
}
#[test]
fn dp_mov_imm() {
assert_eq!(arm("mov r0, 42"), 0xE3A0_002A);
}
#[test]
fn dp_add_regs() {
assert_eq!(arm("add r0, r1, r2"), 0xE081_0002);
}
#[test]
fn dp_sub_imm() {
assert_eq!(arm("sub r3, r3, 1"), 0xE243_3001);
}
#[test]
fn dp_cmp() {
assert_eq!(arm("cmp r0, 0"), 0xE350_0000);
}
#[test]
fn dp_and_reg() {
assert_eq!(arm("and r0, r1, r2"), 0xE001_0002);
}
#[test]
fn dp_orr_imm() {
assert_eq!(arm("orr r0, r0, 0xFF"), 0xE380_00FF);
}
#[test]
fn dp_eor_reg() {
assert_eq!(arm("eor r0, r1, r2"), 0xE021_0002);
}
#[test]
fn dp_bic_imm() {
assert_eq!(arm("bic r0, r0, 0xF"), 0xE3C0_000F);
}
#[test]
fn dp_mvn_reg() {
assert_eq!(arm("mvn r0, r1"), 0xE1E0_0001);
}
#[test]
fn dp_rsb_imm() {
assert_eq!(arm("rsb r0, r0, 0"), 0xE260_0000);
}
#[test]
fn dp_adds_set_flags() {
assert_eq!(arm("adds r0, r1, r2"), 0xE091_0002);
}
#[test]
fn dp_conditional() {
assert_eq!(arm("moveq r0, 1"), 0x03A0_0001);
}
#[test]
fn dp_shifted_reg() {
assert_eq!(arm("add r0, r1, r2, lsl #3"), 0xE081_0182);
assert_eq!(arm("add r0, r1, r2, lsl 3"), 0xE081_0182);
}
#[test]
fn dp_shifted_reg_all_types() {
assert_eq!(arm("sub r0, r1, r2, lsr #4"), 0xE041_0222);
assert_eq!(arm("and r0, r1, r2, asr #2"), 0xE001_0142);
assert_eq!(arm("orr r0, r1, r2, ror #8"), 0xE181_0462);
assert_eq!(arm("add r0, r1, r2, lsl r3"), 0xE081_0312);
assert_eq!(arm("mov r0, r1, rrx"), 0xE1A0_0061);
}
#[test]
fn shift_mnemonics_are_mov_aliases() {
assert_eq!(arm("lsl r0, r1, #4"), arm("mov r0, r1, lsl #4"));
assert_eq!(arm("lsr r0, r1, #4"), arm("mov r0, r1, lsr #4"));
assert_eq!(arm("asr r0, r1, #4"), arm("mov r0, r1, asr #4"));
assert_eq!(arm("ror r0, r1, #4"), arm("mov r0, r1, ror #4"));
assert_eq!(arm("rrx r0, r1"), arm("mov r0, r1, rrx"));
assert_eq!(arm("lsl r0, r1, r2"), arm("mov r0, r1, lsl r2"));
assert_eq!(arm("lsl r0, r1, r2"), 0xE1A0_0211);
}
#[test]
fn shift_without_amount_is_rejected() {
assert!(crate::assemble("add r0, r1, r2, lsl", crate::Arch::Arm).is_err());
}
#[test]
fn dp_two_operand_form() {
assert_eq!(arm("mov r0, r1"), 0xE1A0_0001);
}
#[test]
fn ldr_reg_offset() {
assert_eq!(arm("ldr r0, [r1]"), 0xE591_0000);
}
#[test]
fn str_reg_offset() {
assert_eq!(arm("str r0, [r1]"), 0xE581_0000);
}
#[test]
fn ldr_imm_offset() {
assert_eq!(arm("ldr r0, [r1, 4]"), 0xE591_0004);
}
#[test]
fn ldrb_reg() {
assert_eq!(arm("ldrb r0, [r1]"), 0xE5D1_0000);
}
#[test]
fn strb_reg() {
assert_eq!(arm("strb r0, [r1]"), 0xE5C1_0000);
}
#[test]
fn branch_self() {
let bytes = assemble("b target\ntarget:\nnop", Arch::Arm).unwrap();
let word = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
assert_eq!(word & 0xFF00_0000, 0xEA00_0000); }
#[test]
fn bl_instruction() {
let bytes = assemble("bl target\ntarget:\nnop", Arch::Arm).unwrap();
let word = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
assert_eq!(word & 0xFF00_0000, 0xEB00_0000); }
#[test]
fn bx_lr() {
assert_eq!(arm("bx lr"), 0xE12F_FF1E);
}
#[test]
fn blx_reg() {
assert_eq!(arm("blx r0"), 0xE12F_FF30);
}
#[test]
fn push_single() {
assert_eq!(arm("push {lr}"), 0xE92D_4000);
}
#[test]
fn pop_single() {
assert_eq!(arm("pop {pc}"), 0xE8BD_8000);
}
#[test]
fn push_multi() {
assert_eq!(arm("push {r4, r5, lr}"), 0xE92D_4030);
}
#[test]
fn mul_basic() {
assert_eq!(arm("mul r0, r1, r2"), 0xE000_0291);
}
#[test]
fn mla_basic() {
assert_eq!(arm("mla r0, r1, r2, r3"), 0xE020_3291);
}
#[test]
fn umull_basic() {
assert_eq!(arm("umull r0, r1, r2, r3"), 0xE041_0392);
}
#[test]
fn smull_basic() {
assert_eq!(arm("smull r0, r1, r2, r3"), 0xE001_0392);
}
#[test]
fn movw_imm16() {
assert_eq!(arm("movw r0, 0x1234"), 0xE301_0234);
}
#[test]
fn movt_imm16() {
assert_eq!(arm("movt r0, 0x5678"), 0xE345_0678);
}
#[test]
fn svc_imm() {
assert_eq!(arm("svc 0"), 0xEF00_0000);
}
#[test]
fn nop() {
assert_eq!(arm("nop"), 0xE1A0_0000);
}
#[test]
fn bkpt_imm() {
assert_eq!(arm("bkpt 0"), 0xE120_0070);
}
#[test]
fn clz_basic() {
assert_eq!(arm("clz r0, r1"), 0xE16F_0F11);
}
#[test]
fn rev_basic() {
assert_eq!(arm("rev r0, r1"), 0xE6BF_0F31);
}
#[test]
fn rev16_basic() {
assert_eq!(arm("rev16 r0, r1"), 0xE6BF_0FB1);
}
#[test]
fn rbit_basic() {
assert_eq!(arm("rbit r0, r1"), 0xE6FF_0F31);
}
#[test]
fn uxtb_basic() {
assert_eq!(arm("uxtb r0, r1"), 0xE6EF_0071);
}
#[test]
fn uxth_basic() {
assert_eq!(arm("uxth r0, r1"), 0xE6FF_0071);
}
#[test]
fn sxtb_basic() {
assert_eq!(arm("sxtb r0, r1"), 0xE6AF_0071);
}
#[test]
fn sxth_basic() {
assert_eq!(arm("sxth r0, r1"), 0xE6BF_0071);
}
#[test]
fn ldrh_basic() {
assert_eq!(arm("ldrh r0, [r1]"), 0xE1D1_00B0);
}
#[test]
fn strh_basic() {
assert_eq!(arm("strh r0, [r1]"), 0xE1C1_00B0);
}
#[test]
fn ldrsb_basic() {
assert_eq!(arm("ldrsb r0, [r1]"), 0xE1D1_00D0);
}
#[test]
fn ldrsh_basic() {
assert_eq!(arm("ldrsh r0, [r1]"), 0xE1D1_00F0);
}
#[test]
fn ldrh_preindex() {
assert_eq!(arm("ldrh r0, [r1, 4]!"), 0xE1F1_00B4);
}
#[test]
fn strh_preindex_neg() {
assert_eq!(arm("strh r0, [r1, -8]!"), 0xE16100B8);
}
#[test]
fn ldrh_postindex() {
assert_eq!(arm("ldrh r0, [r1], 4"), 0xE0D1_00B4);
}
#[test]
fn strh_postindex_neg() {
assert_eq!(arm("strh r0, [r1], -4"), 0xE041_00B4);
}
#[test]
fn ldrex_basic() {
assert_eq!(arm("ldrex r0, [r1]"), 0xE191_0F9F);
}
#[test]
fn strex_basic() {
assert_eq!(arm("strex r0, r1, [r2]"), 0xE182_0F91);
}
#[test]
fn dmb_sy() {
assert_eq!(arm("dmb 0xF"), 0xF57F_F05F);
}
#[test]
fn dsb_sy() {
assert_eq!(arm("dsb 0xF"), 0xF57F_F04F);
}
#[test]
fn isb_basic() {
assert_eq!(arm("isb"), 0xF57F_F06F);
}
#[test]
fn mrs_cpsr() {
assert_eq!(arm("mrs r0, cpsr"), 0xE10F_0000);
}
#[test]
fn msr_cpsr_reg() {
assert_eq!(arm("msr cpsr, r0"), 0xE129_F000);
}
#[test]
fn msr_field_selectors() {
assert_eq!(arm("msr cpsr_f, r0"), 0xE128_F000);
assert_eq!(arm("msr cpsr_c, r0"), 0xE121_F000);
assert_eq!(arm("msr cpsr_fsxc, r0"), 0xE12F_F000);
assert_eq!(arm("msr spsr_f, r0"), 0xE168_F000);
assert_eq!(arm("mrs r0, spsr"), 0xE14F_0000);
assert_eq!(arm("mrs r0, cpsr"), 0xE10F_0000);
}
#[test]
fn ldmia_basic() {
assert_eq!(arm("ldmia r0, {r1, r2}"), 0xE890_0006);
}
#[test]
fn stmdb_basic() {
assert_eq!(arm("stmdb sp!, {r4, lr}"), 0xE92D_4010);
}
#[test]
fn bfc_r0_4_8() {
assert_eq!(arm("bfc r0, 4, 8"), 0xE7CB_021F);
}
#[test]
fn bfi_r0_r1_0_8() {
assert_eq!(arm("bfi r0, r1, 0, 8"), 0xE7C7_0011);
}
#[test]
fn sbfx_r0_r1_4_8() {
assert_eq!(arm("sbfx r0, r1, 4, 8"), 0xE7A7_0251);
}
#[test]
fn ubfx_r0_r1_4_8() {
assert_eq!(arm("ubfx r0, r1, 4, 8"), 0xE7E7_0251);
}
#[test]
fn bfc_high_bits() {
assert_eq!(arm("bfc r3, 16, 16"), 0xE7DF_381F);
}
#[test]
fn bfi_conditional() {
assert_eq!(arm("bfine r2, r3, 8, 4"), 0x17CB_2413);
}
#[test]
fn unknown_mnemonic() {
let err = assemble("xyz", Arch::Arm).unwrap_err();
assert!(matches!(
err,
crate::error::AsmError::UnknownMnemonic { .. }
));
}
#[test]
fn dp_bad_operand() {
let err = assemble("add r0, r1", Arch::Arm).unwrap_err();
assert!(matches!(
err,
crate::error::AsmError::InvalidOperands { .. } | crate::error::AsmError::Syntax { .. }
));
}
#[test]
fn mov_small_uses_dp_form() {
let code = assemble("mov r0, 0xFF", Arch::Arm).unwrap();
assert_eq!(code.len(), 4, "small immediate should be 4 bytes (DP form)");
}
#[test]
fn mov_non_encodable_uses_movw() {
let code = assemble("mov r0, 0x1234", Arch::Arm).unwrap();
assert_eq!(code.len(), 4, "16-bit immediate should be single MOVW");
let word = u32::from_le_bytes(code[0..4].try_into().unwrap());
assert_eq!(
(word >> 20) & 0xFF,
0x30,
"opcode should be MOVW (0011_0000)"
);
assert_eq!((word >> 12) & 0xF, 0);
let imm4 = (word >> 16) & 0xF;
let imm12 = word & 0xFFF;
let reconstructed = (imm4 << 12) | imm12;
assert_eq!(reconstructed, 0x1234);
}
#[test]
fn mov_large_uses_movw_movt() {
let code = assemble("mov r0, 0x12345678", Arch::Arm).unwrap();
assert_eq!(code.len(), 8, "large immediate needs MOVW+MOVT pair");
let w0 = u32::from_le_bytes(code[0..4].try_into().unwrap());
assert_eq!((w0 >> 20) & 0xFF, 0x30, "first should be MOVW");
let lo_imm4 = (w0 >> 16) & 0xF;
let lo_imm12 = w0 & 0xFFF;
assert_eq!((lo_imm4 << 12) | lo_imm12, 0x5678);
let w1 = u32::from_le_bytes(code[4..8].try_into().unwrap());
assert_eq!((w1 >> 20) & 0xFF, 0x34, "second should be MOVT");
let hi_imm4 = (w1 >> 16) & 0xF;
let hi_imm12 = w1 & 0xFFF;
assert_eq!((hi_imm4 << 12) | hi_imm12, 0x1234);
}
#[test]
fn mov_16bit_only_no_movt() {
let code = assemble("mov r0, 0xFFFF", Arch::Arm).unwrap();
assert_eq!(code.len(), 4, "16-bit value needs only MOVW, no MOVT");
}
#[test]
fn mov_rotatable_still_uses_dp() {
let code = assemble("mov r0, 0xFF00", Arch::Arm).unwrap();
assert_eq!(code.len(), 4, "rotatable immediate should use DP form");
let word = u32::from_le_bytes(code[0..4].try_into().unwrap());
assert_eq!((word >> 25) & 0b111, 0b001, "should be DP immediate form");
}
#[test]
fn mov_0x1234_different_regs() {
for (src, expected_rd) in &[
("mov r0, 0x1234", 0u32),
("mov r5, 0x1234", 5),
("mov r12, 0x1234", 12),
] {
let code = assemble(src, Arch::Arm).unwrap();
let word = u32::from_le_bytes(code[0..4].try_into().unwrap());
assert_eq!(
(word >> 12) & 0xF,
*expected_rd,
"Rd mismatch for '{}'",
src
);
}
}
#[test]
fn mov_conditional_movw_fallback() {
let code = assemble("movne r0, 0x1234", Arch::Arm).unwrap();
let word = u32::from_le_bytes(code[0..4].try_into().unwrap());
assert_eq!((word >> 28) & 0xF, 0x1, "condition should be NE (0x1)");
}
#[test]
fn mvn_non_encodable_uses_movw_complement() {
let code = assemble("mvn r0, 0x1234", Arch::Arm).unwrap();
assert_eq!(
code.len(),
8,
"MVN fallback should emit MOVW+MOVT for complement"
);
let w0 = u32::from_le_bytes(code[0..4].try_into().unwrap());
let lo_imm4 = (w0 >> 16) & 0xF;
let lo_imm12 = w0 & 0xFFF;
let lo = (lo_imm4 << 12) | lo_imm12;
assert_eq!(lo, 0xEDCB, "lo16 of ~0x1234");
let w1 = u32::from_le_bytes(code[4..8].try_into().unwrap());
let hi_imm4 = (w1 >> 16) & 0xF;
let hi_imm12 = w1 & 0xFFF;
let hi = (hi_imm4 << 12) | hi_imm12;
assert_eq!(hi, 0xFFFF, "hi16 of ~0x1234");
}
#[test]
fn add_non_encodable_still_errors() {
let err = assemble("add r0, r0, 0x1234", Arch::Arm).unwrap_err();
assert!(matches!(
err,
crate::error::AsmError::InvalidOperands { .. }
));
}
#[test]
fn all_encodable_modified_immediates_roundtrip() {
let mut seen = std::collections::HashSet::new();
for rot in 0..16u32 {
for imm8 in 0..=0xFFu32 {
let val = imm8.rotate_right(rot * 2);
if seen.insert(val) {
let src = alloc::format!("mov r0, {}", val);
let code = assemble(&src, Arch::Arm)
.unwrap_or_else(|e| panic!("failed to assemble '{}': {:?}", src, e));
assert_eq!(
code.len(),
4,
"encodable modified immediate {} should use DP form (4 bytes)",
val
);
let word = u32::from_le_bytes(code[0..4].try_into().unwrap());
assert_eq!(
(word >> 25) & 0b111,
0b001,
"bit 25 should be set for DP immediate form, val={}",
val
);
}
}
}
assert!(
seen.len() > 100,
"should find >100 unique encodable values, found {}",
seen.len()
);
}
#[test]
fn all_u32_mov_succeeds() {
let test_values: Vec<u32> = vec![
0, 1, 0xFF, 0x100, 0x1234, 0xFFFF, 0x10000, 0x12345678, 0xDEADBEEF, 0xFFFFFFFF,
0x80000000, 0x7FFFFFFF, 0xCAFEBABE, 0x00FF00FF, 0xFF00FF00,
];
for val in &test_values {
let src = alloc::format!("mov r0, {}", val);
let code = assemble(&src, Arch::Arm)
.unwrap_or_else(|e| panic!("MOV R0, {} should succeed: {:?}", val, e));
assert!(
code.len() == 4 || code.len() == 8,
"MOV R0, {}: expected 4 or 8 bytes, got {}",
val,
code.len()
);
}
}
#[test]
fn non_encodable_dp_errors_for_non_mov() {
for mnemonic in &["add", "sub", "and", "orr", "eor", "bic"] {
let src = alloc::format!("{} r0, r0, 0x1234", mnemonic);
let result = assemble(&src, Arch::Arm);
assert!(
result.is_err(),
"{} with non-encodable immediate should error",
mnemonic
);
}
}
fn thumb16(src: &str) -> u16 {
let bytes = assemble(src, Arch::Thumb).unwrap();
assert_eq!(
bytes.len(),
2,
"expected 2-byte Thumb, got {} bytes for: {}",
bytes.len(),
src
);
u16::from_le_bytes([bytes[0], bytes[1]])
}
fn thumb32(src: &str) -> (u16, u16) {
let bytes = assemble(src, Arch::Thumb).unwrap();
assert_eq!(
bytes.len(),
4,
"expected 4-byte Thumb-2, got {} bytes for: {}",
bytes.len(),
src
);
let hw1 = u16::from_le_bytes([bytes[0], bytes[1]]);
let hw2 = u16::from_le_bytes([bytes[2], bytes[3]]);
(hw1, hw2)
}
#[test]
fn thumb_nop() {
assert_eq!(thumb16("nop"), 0xBF00);
}
#[test]
fn thumb_bkpt() {
assert_eq!(thumb16("bkpt 0"), 0xBE00);
assert_eq!(thumb16("bkpt 255"), 0xBEFF);
}
#[test]
fn thumb_svc() {
assert_eq!(thumb16("svc 0"), 0xDF00);
assert_eq!(thumb16("svc 1"), 0xDF01);
}
#[test]
fn thumb_bx_lr() {
assert_eq!(thumb16("bx lr"), 0x4770);
}
#[test]
fn thumb_bx_r0() {
assert_eq!(thumb16("bx r0"), 0x4700);
}
#[test]
fn thumb_blx_r0() {
assert_eq!(thumb16("blx r0"), 0x4780);
}
#[test]
fn thumb_mov_imm8() {
assert_eq!(thumb16("mov r0, 0"), 0x2000);
assert_eq!(thumb16("mov r0, 42"), 0x202A);
assert_eq!(thumb16("mov r7, 255"), 0x27FF);
}
#[test]
fn thumb_mov_reg_high() {
assert_eq!(thumb16("mov r0, r8"), 0x4640);
}
#[test]
fn thumb_mov_reg_low() {
assert_eq!(thumb16("movs r0, r1"), 0x0008);
}
#[test]
fn thumb_add_imm3() {
assert_eq!(thumb16("add r0, r1, 3"), 0x1CC8);
}
#[test]
fn thumb_add_imm8() {
assert_eq!(thumb16("add r0, 42"), 0x302A);
}
#[test]
fn thumb_add_reg3() {
assert_eq!(thumb16("add r0, r1, r2"), 0x1888);
}
#[test]
fn thumb_sub_imm3() {
assert_eq!(thumb16("sub r0, r1, 3"), 0x1EC8);
}
#[test]
fn thumb_sub_imm8() {
assert_eq!(thumb16("sub r0, 42"), 0x382A);
}
#[test]
fn thumb_sub_reg3() {
assert_eq!(thumb16("sub r0, r1, r2"), 0x1A88);
}
#[test]
fn thumb_cmp_imm8() {
assert_eq!(thumb16("cmp r0, 42"), 0x282A);
}
#[test]
fn thumb_cmp_reg_lo() {
assert_eq!(thumb16("cmp r0, r1"), 0x4288);
}
#[test]
fn thumb_cmp_reg_hi() {
assert_eq!(thumb16("cmp r0, r8"), 0x4540);
}
#[test]
fn thumb_and_reg() {
assert_eq!(thumb16("and r0, r1"), 0x4008);
}
#[test]
fn thumb_orr_reg() {
assert_eq!(thumb16("orr r0, r1"), 0x4308);
}
#[test]
fn thumb_eor_reg() {
assert_eq!(thumb16("eor r0, r1"), 0x4048);
}
#[test]
fn thumb_bic_reg() {
assert_eq!(thumb16("bic r0, r1"), 0x4388);
}
#[test]
fn thumb_mvn_reg() {
assert_eq!(thumb16("mvn r0, r1"), 0x43C8);
}
#[test]
fn thumb_mul_reg() {
assert_eq!(thumb16("mul r0, r1"), 0x4348);
}
#[test]
fn thumb_tst_reg() {
assert_eq!(thumb16("tst r0, r1"), 0x4208);
}
#[test]
fn thumb_lsl_imm5() {
assert_eq!(thumb16("lsl r0, r1, 3"), 0x00C8);
}
#[test]
fn thumb_lsr_imm5() {
assert_eq!(thumb16("lsr r0, r1, 3"), 0x08C8);
}
#[test]
fn thumb_asr_imm5() {
assert_eq!(thumb16("asr r0, r1, 3"), 0x10C8);
}
#[test]
fn thumb_ldr_imm5() {
assert_eq!(thumb16("ldr r0, [r1, 0]"), 0x6808);
assert_eq!(thumb16("ldr r0, [r1, 4]"), 0x6848);
}
#[test]
fn thumb_str_imm5() {
assert_eq!(thumb16("str r0, [r1, 0]"), 0x6008);
}
#[test]
fn thumb_ldrb_imm5() {
assert_eq!(thumb16("ldrb r0, [r1, 0]"), 0x7808);
}
#[test]
fn thumb_strb_imm5() {
assert_eq!(thumb16("strb r0, [r1, 0]"), 0x7008);
}
#[test]
fn thumb_ldrh_imm5() {
assert_eq!(thumb16("ldrh r0, [r1, 0]"), 0x8808);
}
#[test]
fn thumb_ldr_sp_rel() {
assert_eq!(thumb16("ldr r0, [sp, 0]"), 0x9800);
assert_eq!(thumb16("ldr r0, [sp, 4]"), 0x9801);
}
#[test]
fn thumb_str_sp_rel() {
assert_eq!(thumb16("str r0, [sp, 0]"), 0x9000);
}
#[test]
fn thumb_push() {
assert_eq!(thumb16("push {r0, r1, lr}"), 0xB503);
assert_eq!(thumb16("push {r0}"), 0xB401);
}
#[test]
fn thumb_pop() {
assert_eq!(thumb16("pop {r0, r1, pc}"), 0xBD03);
assert_eq!(thumb16("pop {r0}"), 0xBC01);
}
#[test]
fn thumb_it_eq() {
let bytes = assemble("it eq", Arch::Thumb).unwrap();
assert_eq!(bytes.len(), 2);
let hw = u16::from_le_bytes([bytes[0], bytes[1]]);
assert_eq!(hw, 0xBF08);
}
#[test]
fn thumb_ite_ne() {
let bytes = assemble("ite ne", Arch::Thumb).unwrap();
assert_eq!(bytes.len(), 2);
let hw = u16::from_le_bytes([bytes[0], bytes[1]]);
assert_eq!(hw, 0xBF14);
}
#[test]
fn thumb_add_high_reg() {
assert_eq!(thumb16("add r0, r8"), 0x4440);
}
#[test]
fn thumb_neg_reg() {
assert_eq!(thumb16("neg r0, r1"), 0x4248);
}
#[test]
fn thumb_adc_reg() {
assert_eq!(thumb16("adc r0, r1"), 0x4148);
}
#[test]
fn thumb_sbc_reg() {
assert_eq!(thumb16("sbc r0, r1"), 0x4188);
}
#[test]
fn thumb_ror_reg() {
assert_eq!(thumb16("ror r0, r1"), 0x41C8);
}
#[test]
fn thumb_cmn_reg() {
assert_eq!(thumb16("cmn r0, r1"), 0x42C8);
}
#[test]
fn thumb_ldr_reg_offset() {
assert_eq!(thumb16("ldr r0, [r1, r2]"), 0x5888);
}
#[test]
fn thumb_str_reg_offset() {
assert_eq!(thumb16("str r0, [r1, r2]"), 0x5088);
}
#[test]
fn thumb_bl_label() {
let bytes = assemble("bl target\ntarget:", Arch::Thumb).unwrap();
assert_eq!(bytes.len(), 4, "BL should be 4 bytes");
}
#[test]
fn thumb_b_label_short() {
let bytes = assemble("b target\ntarget:", Arch::Thumb).unwrap();
assert_eq!(
bytes.len(),
2,
"B to nearby label should use narrow encoding"
);
}
#[test]
fn thumb_beq_label_short() {
let bytes = assemble("beq target\ntarget:", Arch::Thumb).unwrap();
assert_eq!(
bytes.len(),
2,
"BEQ to nearby label should use narrow encoding"
);
}
#[test]
fn thumb_high_reg_not_allowed_in_narrow_dp() {
let result = assemble("and r0, r8", Arch::Thumb);
assert!(result.is_err());
}
#[test]
fn thumb_bkpt_range() {
let result = assemble("bkpt 256", Arch::Thumb);
assert!(result.is_err());
}
#[test]
fn thumb_ldrb_reg_offset() {
assert_eq!(thumb16("ldrb r0, [r1, r2]"), 0x5C88);
}
#[test]
fn thumb_strb_reg_offset() {
assert_eq!(thumb16("strb r0, [r1, r2]"), 0x5488);
}
#[test]
fn thumb_ldrh_reg_offset() {
assert_eq!(thumb16("ldrh r0, [r1, r2]"), 0x5A88);
}
#[test]
fn thumb_strh_reg_offset() {
assert_eq!(thumb16("strh r0, [r1, r2]"), 0x5288);
}
#[test]
fn thumb_strh_imm5() {
assert_eq!(thumb16("strh r0, [r1, 0]"), 0x8008);
}
#[test]
fn thumb_lsl_reg() {
assert_eq!(thumb16("lsl r0, r1"), 0x4088);
}
#[test]
fn thumb_lsr_reg() {
assert_eq!(thumb16("lsr r0, r1"), 0x40C8);
}
#[test]
fn thumb_asr_reg() {
assert_eq!(thumb16("asr r0, r1"), 0x4108);
}
#[test]
fn thumb_bl_encoding() {
let bytes = assemble("bl target\ntarget: nop", Arch::Thumb).unwrap();
assert_eq!(bytes.len(), 6);
let hw1 = u16::from_le_bytes([bytes[0], bytes[1]]);
let hw2 = u16::from_le_bytes([bytes[2], bytes[3]]);
assert_eq!(hw1 >> 11, 0b11110);
assert!(hw2 & 0xD000 == 0xD000);
}
#[test]
fn thumb_bl_self_ref() {
let (hw1, hw2) = thumb32("here: bl here");
assert_eq!(hw1 >> 11, 0b11110);
assert!(hw2 & 0xD000 == 0xD000);
}
}