kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit e76d974a034d26387ed146730d0366f5fa274d5b
parent 4473fe52f7a6196d01f1c467b100c5ebe061517c
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue, 16 Jun 2026 17:49:00 -0700

arm32 Phase 2: descriptor-driven disassembler + standalone assembler

isa.c descriptor table (16/32-bit Thumb-2 families) + disasm.c (decode_one/block,
formatter, ArchDecodeOps, IT-state, Thumb-2 width detection) + asm.c (operand
parse/encode, .syntax unified) wired into arch_impl_arm32 (.decode/.asm_ops) +
test/arch/arm32_decode_test (test-isa). Integrated against the op-group/atomics
tracks: deduped the shared encoders (kept the backend's 16-bit REV that native.c
emits; added T's unique MLA/UMULL/SMULL the assembler needs), kept ARM_FP=r7 (no
enum alias), and the decode test now exercises the 16-bit REV form kit emits.

Diffstat:
Mmk/test.mk | 5++++-
Mmk/test_unit.mk | 4+++-
Msrc/arch/arm32/arch.c | 7+++++--
Msrc/arch/arm32/asm.c | 856++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/arch/arm32/disasm.c | 461+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Asrc/arch/arm32/disasm.h | 17+++++++++++++++++
Asrc/arch/arm32/isa.c | 1109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/arch/arm32/isa.h | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/arch/arm32_decode_test.c | 357+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 2948 insertions(+), 21 deletions(-)

diff --git a/mk/test.mk b/mk/test.mk @@ -403,11 +403,14 @@ test-dbg-red: bin AA64_ISA_TEST_BIN = build/test/aa64_isa_test RV64_DECODE_TEST_BIN = build/test/rv64_decode_test RV32_DECODE_TEST_BIN = build/test/rv32_decode_test +ARM32_DECODE_TEST_BIN = build/test/arm32_decode_test -test-isa: $(AA64_ISA_TEST_BIN) $(RV64_DECODE_TEST_BIN) $(RV32_DECODE_TEST_BIN) +test-isa: $(AA64_ISA_TEST_BIN) $(RV64_DECODE_TEST_BIN) $(RV32_DECODE_TEST_BIN) \ + $(ARM32_DECODE_TEST_BIN) $(AA64_ISA_TEST_BIN) $(RV64_DECODE_TEST_BIN) $(RV32_DECODE_TEST_BIN) + $(ARM32_DECODE_TEST_BIN) diff --git a/mk/test_unit.mk b/mk/test_unit.mk @@ -56,7 +56,8 @@ x64_inline_test_SRC := test/arch/x64_inline_test.c UNIT_TESTS_INTERNAL := \ dwarf_test debug_roundtrip_unit debug_cfi_unit \ - aa64_isa_test rv64_decode_test rv32_decode_test aa64_sweep_gen \ + aa64_isa_test rv64_decode_test rv32_decode_test arm32_decode_test \ + aa64_sweep_gen \ reloc_uleb128_unit reloc_desc_test reloc_apply_test emu_rv64_unit_test \ interp_smoke_test jit_tls_relax_test coff_weak_alias_test \ coff_archive_fixpoint_test elf_version_import_test \ @@ -68,6 +69,7 @@ debug_cfi_unit_SRC := test/debug/cfi_unit.c aa64_isa_test_SRC := test/arch/aa64_isa_test.c rv64_decode_test_SRC := test/arch/rv64_decode_test.c rv32_decode_test_SRC := test/arch/rv32_decode_test.c +arm32_decode_test_SRC := test/arch/arm32_decode_test.c aa64_sweep_gen_SRC := test/arch/aa64_sweep_gen.c reloc_uleb128_unit_SRC := test/link/reloc_uleb128_unit.c reloc_desc_test_SRC := test/link/reloc_desc_test.c diff --git a/src/arch/arm32/arch.c b/src/arch/arm32/arch.c @@ -20,6 +20,8 @@ extern const LinkArchDesc link_arch_arm32; extern const ArchDbgOps arm32_dbg_ops; extern const ArchDwarfOps arm32_dwarf_ops; +extern const ArchDecodeOps arm32_decode_ops; +extern const ArchAsmOps arm32_asm_ops; extern ArchAsm* arm32_arch_asm_new(Compiler*); extern ArchDisasm* arm32_disasm_new(Compiler*); @@ -263,11 +265,12 @@ const ArchImpl arch_impl_arm32 = { .asm_new = arm32_arch_asm_new, .disasm_new = arm32_disasm_new, .apply_label_fixup = arm32_apply_label_fixup, - /* .decode / .emu omitted (NULL) for Phase 1 — like aa64. */ + .decode = &arm32_decode_ops, + /* .emu omitted (NULL) — no emu lifter for arm32 yet (like aa64/x64). */ .link = &link_arch_arm32, .dwarf = &arm32_dwarf_ops, .dbg = &arm32_dbg_ops, - .asm_ops = NULL, /* numeric operand printing until the asm frontend lands */ + .asm_ops = &arm32_asm_ops, .predefined_macros = arm32_predefined_macros, .npredefined_macros = (u32)(sizeof arm32_predefined_macros / sizeof arm32_predefined_macros[0]), diff --git a/src/arch/arm32/asm.c b/src/arch/arm32/asm.c @@ -1,14 +1,854 @@ /* ARM32 (Thumb-2) textual-assembler frontend. * - * Phase 1 (the walking skeleton) ships no standalone assembler: the qemu - * smoke lane's reset stub is clang-assembled, and the smoke C set uses no - * inline asm. The `.syntax unified` operand parser + IT-block syntax + - * byte-golden lane are a Phase-2 deliverable (ARM32.md "Standalone assembler - * frontend" / "Inline asm"). arm32_arch_asm_new yields no assembler until - * then; the ArchImpl leaves .asm_ops NULL (numeric operand printing). */ + * Implements the per-instruction `insn` hook the shared assembler driver + * (src/asm/asm.c) calls once per source line. Operands are parsed off the + * AsmDriver token stream and encoded via the isa.h inline encoders, emitting + * each Thumb-2 instruction as little-endian half-words (hw1 first for the + * 32-bit forms). Mirrors src/arch/riscv/asm.c (descriptor lookup + format + * dispatch) and src/arch/aa64/asm.c (the ArchAsmOps printer-side seam). + * + * Mnemonic resolution: the driver composes dotted mnemonics (so `mov.w`, + * `b.w`, `ldr.w` arrive whole). A condition-code suffix (`bne`, `beq.w`, + * `moveq`) is stripped here to recover the base mnemonic + condition. The + * `.syntax unified` / `.thumb` directives are consumed by the shared driver. */ #include "arch/arch.h" +#include <string.h> + +#include "arch/arm32/isa.h" +#include "arch/arm32/regs.h" +#include "asm/asm_helpers.h" +#include "asm/asm_lex.h" +#include "core/arena.h" +#include "core/pool.h" +#include "core/slice.h" + +typedef struct Arm32Asm { + ArchAsm base; + Compiler* c; +} Arm32Asm; + +/* ---- byte emit (LE half-words, hw1 first for 32-bit) ---- */ +static void emit_t16(AsmDriver* d, u16 hw) { + u8 b[2] = {(u8)(hw & 0xffu), (u8)((hw >> 8) & 0xffu)}; + mc_emit_bytes(asm_driver_mc(d), b, sizeof b); +} +static void emit_t32(AsmDriver* d, u32 instr) { + u32 hw1 = (instr >> 16) & 0xffffu, hw2 = instr & 0xffffu; + u8 b[4] = {(u8)(hw1 & 0xffu), (u8)((hw1 >> 8) & 0xffu), (u8)(hw2 & 0xffu), + (u8)((hw2 >> 8) & 0xffu)}; + mc_emit_bytes(asm_driver_mc(d), b, sizeof b); +} + +/* Construct a Slice (KitSlice's first member is a union, so the brace-init form + * needs nesting; this helper keeps the call sites clean). */ +static Slice arm_slice(const char* s, size_t len) { + Slice sl; + sl.s = s; + sl.len = len; + return sl; +} + +/* ---- operand parse helpers ---- */ +static int sym_to_cstr(AsmDriver* d, Sym s, char* out, size_t cap) { + Slice sl = pool_slice(asm_driver_pool(d), s); + if (!sl.s || sl.len >= cap) return 0; + memcpy(out, sl.s, sl.len); + out[sl.len] = '\0'; + return 1; +} + +static u32 parse_reg(AsmDriver* d) { + AsmTok t = asm_driver_next(d); + char name[16]; + uint32_t idx = 0; + if (t.kind != ASM_TOK_IDENT || !sym_to_cstr(d, t.v.ident, name, sizeof name) || + arm32_register_index(name, &idx) != 0 || idx > 15u) + asm_driver_panic(d, "arm32 asm: bad core register"); + return idx; +} + +static void expect_comma(AsmDriver* d) { + if (!asm_driver_eat_comma(d)) asm_driver_panic(d, "arm32 asm: expected ','"); +} + +/* 1 if the next token is a core register name (used to disambiguate the + * register vs modified-immediate data-processing forms by operand shape). */ +static int peek_is_reg(AsmDriver* d) { + AsmTok t = asm_driver_peek(d); + char name[16]; + uint32_t idx; + if (t.kind != ASM_TOK_IDENT) return 0; + if (!sym_to_cstr(d, t.v.ident, name, sizeof name)) return 0; + return arm32_register_index(name, &idx) == 0 && idx <= 15u; +} + +/* Parse a #-prefixed immediate constant. The `#` is optional (GNU as accepts + * both); a bare expression is also accepted. */ +static i64 parse_imm(AsmDriver* d) { + (void)asm_driver_eat_punct(d, '#'); + return asm_driver_parse_const(d); +} + +/* A modified-immediate operand: encode via thumb_expand_imm_encode, panicking + * if the value isn't representable (the assembler does not materialize). */ +static u32 parse_modimm(AsmDriver* d) { + i64 v = parse_imm(d); + u32 out12; + if (!thumb_expand_imm_encode((u32)v, &out12)) + asm_driver_panic(d, "arm32 asm: immediate not a modified-immediate"); + return out12; +} + +/* `[Rn]` / `[Rn, #imm]` — returns base in *base_out, signed displacement in + * *disp_out. Pre/post-index and shifted-index modes are a follow-on. */ +static void parse_mem(AsmDriver* d, u32* base_out, i64* disp_out) { + asm_driver_expect_punct(d, '[', "'[' in arm32 memory operand"); + *base_out = parse_reg(d); + *disp_out = 0; + if (asm_driver_eat_comma(d)) *disp_out = parse_imm(d); + asm_driver_expect_punct(d, ']', "']' in arm32 memory operand"); +} + +/* Register list `{r0, r1, lr}` -> bitmask. */ +static u32 parse_reglist(AsmDriver* d) { + u32 mask = 0; + asm_driver_expect_punct(d, '{', "'{' in arm32 register list"); + for (;;) { + u32 r = parse_reg(d); + mask |= (1u << r); + if (!asm_driver_eat_comma(d)) break; + } + asm_driver_expect_punct(d, '}', "'}' in arm32 register list"); + return mask; +} + +/* `#:lower16:sym` / `#:upper16:sym` modifier -> 1 + reloc kind, sym, addend. + * The leading `#` is optional; returns 0 if no `:modifier:` is present. */ +static int parse_movw_mod(AsmDriver* d, RelocKind* kind_out, ObjSymId* sym_out, + i64* off_out, int is_movt) { + (void)asm_driver_eat_punct(d, '#'); + if (!asm_driver_tok_is_punct(asm_driver_peek(d), ':')) return 0; + (void)asm_driver_next(d); /* ':' */ + { + AsmTok name = asm_driver_next(d); + Slice s; + RelocKind k; + if (name.kind != ASM_TOK_IDENT) + asm_driver_panic(d, "arm32 asm: expected relocation modifier"); + s = pool_slice(asm_driver_pool(d), name.v.ident); + if (slice_eq_cstr(s, "lower16")) + k = R_ARM_THM_MOVW_ABS_NC; + else if (slice_eq_cstr(s, "upper16")) + k = R_ARM_THM_MOVT_ABS; + else + asm_driver_panic(d, "arm32 asm: unsupported relocation modifier"); + if ((k == R_ARM_THM_MOVT_ABS) != (is_movt != 0)) + asm_driver_panic(d, "arm32 asm: lower16/upper16 mismatched with movw/movt"); + asm_driver_expect_punct(d, ':', "':' closing relocation modifier"); + { + ObjSymId sym = OBJ_SYM_NONE; + i64 off = 0; + asm_driver_parse_sym_expr(d, &sym, &off); + *kind_out = k; + *sym_out = sym; + *off_out = off; + } + return 1; + } +} + +/* Optional shift `, lsl #n` etc. on a register operand; returns the shift type + * (0..3) and amount, 0/0 if absent. */ +static u32 parse_opt_shift(AsmDriver* d, u32* amount_out) { + Slice s; + AsmTok t; + *amount_out = 0; + t = asm_driver_peek(d); + if (t.kind != ASM_TOK_IDENT) return 0; + s = pool_slice(asm_driver_pool(d), t.v.ident); + u32 type; + if (slice_eq_cstr(s, "lsl")) + type = 0; + else if (slice_eq_cstr(s, "lsr")) + type = 1; + else if (slice_eq_cstr(s, "asr")) + type = 2; + else if (slice_eq_cstr(s, "ror")) + type = 3; + else + return 0; + (void)asm_driver_next(d); /* the shift mnemonic */ + *amount_out = (u32)parse_imm(d); + return type; +} + +/* Barrier option: `sy` (full system) or a bare numeric. */ +static u32 parse_barrier_opt(AsmDriver* d) { + AsmTok t = asm_driver_peek(d); + if (t.kind == ASM_TOK_IDENT) { + Slice s = pool_slice(asm_driver_pool(d), t.v.ident); + if (slice_eq_cstr(s, "sy")) { + (void)asm_driver_next(d); + return 0xfu; + } + } + if (asm_driver_at_eol(d)) return 0xfu; /* default SY */ + return (u32)asm_driver_parse_const(d) & 0xfu; +} + +/* ===================================================================== + * Mnemonic resolution: strip a trailing condition-code suffix to recover the + * base mnemonic + condition (e.g. `bne`->`b`/ne, `beq.w`->`b.w`/eq, + * `moveq`->`mov`/eq). Returns the base descriptor and writes *cond_out + * (ARM_CC_AL if unconditional). The condition is encoded only by the branch + * formats; DP/etc. conditional forms require an enclosing IT block and the + * assembler accepts the suffix but the IT instruction supplies the predicate. + * ===================================================================== */ +static const Arm32InsnDesc* resolve_mnemonic(AsmDriver* d, Slice mn, + u32* cond_out) { + const Arm32InsnDesc* desc = arm32_asm_find(mn); + *cond_out = ARM_CC_AL; + if (desc) return desc; + /* Try a trailing ".w" + 2-char cond (e.g. "bne.w" -> base "b.w"). */ + if (mn.len >= 4 && mn.s[mn.len - 2] == '.' && mn.s[mn.len - 1] == 'w') { + int cv = arm32_cond_from_name(arm_slice(mn.s + mn.len - 4, 2)); + if (cv >= 0) { + char buf[24]; + size_t base = mn.len - 4; + if (base + 2 < sizeof buf) { + memcpy(buf, mn.s, base); + buf[base] = '.'; + buf[base + 1] = 'w'; + desc = arm32_asm_find(arm_slice(buf, base + 2)); + if (desc) { + *cond_out = (u32)cv; + return desc; + } + } + } + } + /* Try a trailing 2-char cond (e.g. "bne" -> "b", "moveq" -> "mov"). */ + if (mn.len >= 2) { + int cv = arm32_cond_from_name(arm_slice(mn.s + mn.len - 2, 2)); + if (cv >= 0) { + desc = arm32_asm_find(arm_slice(mn.s, mn.len - 2)); + if (desc) { + *cond_out = (u32)cv; + return desc; + } + } + } + asm_driver_panic(d, "arm32 asm: unsupported instruction"); +} + +/* Encode + emit one instruction for the matched descriptor. */ +static void assemble_one(AsmDriver* d, const Arm32InsnDesc* desc, u32 cond) { + Slice mn = desc->mnemonic; + switch ((Arm32Format)desc->fmt) { + case ARM_FMT_NONE: { + emit_t16(d, (u16)(desc->match & 0xffffu)); + return; + } + case ARM_FMT_DP_REG: + case ARM_FMT_SHIFT_REG: { + u32 rd = parse_reg(d); + u32 rn, rm; + expect_comma(d); + rn = parse_reg(d); + expect_comma(d); + rm = parse_reg(d); + /* hw1 = base|rn, hw2 = (rd<<8)|rm (shift-reg uses hw2 0xF000 base). */ + if ((Arm32Format)desc->fmt == ARM_FMT_SHIFT_REG) + emit_t32(d, desc->match | (rn << 16) | (rd << 8) | rm); + else + emit_t32(d, desc->match | (rn << 16) | (rd << 8) | rm); + return; + } + case ARM_FMT_DP_IMM: { + /* The mnemonic table prefers the modified-immediate row, but `add.w r0, + * r1, r2` etc. take a register third operand. Dispatch on shape: a + * register operand emits the shifted-register form (hw1 0xEAxx; the + * op4/S selector bits sit in the same positions as the imm form). */ + u32 rd = parse_reg(d), rn; + expect_comma(d); + rn = parse_reg(d); + expect_comma(d); + if (peek_is_reg(d)) { + u32 rm = parse_reg(d); + u32 base = (desc->match & ~(0xf000u << 16)) | (0xea00u << 16); + emit_t32(d, base | (rn << 16) | (rd << 8) | rm); + } else { + u32 out12 = parse_modimm(d); + u32 i = (out12 >> 11) & 1u, imm3 = (out12 >> 8) & 7u, + imm8 = out12 & 0xffu; + emit_t32(d, desc->match | (i << 26) | (rn << 16) | (imm3 << 12) | + (rd << 8) | imm8); + } + return; + } + case ARM_FMT_MOV_IMM: { + /* `mov.w rd, #imm` (this row) or `mov.w rd, rm` (register form). The + * register form is ORR rd, 1111, rm (hw1 0xEA4F for MOV / 0xEA6F MVN). */ + u32 rd = parse_reg(d); + expect_comma(d); + if (peek_is_reg(d)) { + u32 rm = parse_reg(d); + u32 mvn = slice_eq_cstr(mn, "mvn.w"); + emit_t32(d, arm_t32(mvn ? 0xea6fu : 0xea4fu, (rd << 8) | rm)); + return; + } + { + u32 out12 = parse_modimm(d); + u32 i = (out12 >> 11) & 1u, imm3 = (out12 >> 8) & 7u, + imm8 = out12 & 0xffu; + emit_t32(d, desc->match | (i << 26) | (imm3 << 12) | (rd << 8) | imm8); + } + return; + } + case ARM_FMT_MOV_REG: { + u32 rd = parse_reg(d), rm; + expect_comma(d); + rm = parse_reg(d); + emit_t32(d, desc->match | (rd << 8) | rm); + return; + } + case ARM_FMT_CMP_REG: { + u32 rn = parse_reg(d), rm; + expect_comma(d); + rm = parse_reg(d); + emit_t32(d, desc->match | (rn << 16) | rm); + return; + } + case ARM_FMT_CMP_IMM: { + u32 rn = parse_reg(d), out12; + expect_comma(d); + if (peek_is_reg(d)) { + u32 rm = parse_reg(d); + u32 base = (desc->match & ~(0xf000u << 16)) | (0xea00u << 16); + emit_t32(d, base | (rn << 16) | rm); + return; + } + out12 = parse_modimm(d); + { + u32 i = (out12 >> 11) & 1u, imm3 = (out12 >> 8) & 7u, + imm8 = out12 & 0xffu; + emit_t32(d, desc->match | (i << 26) | (rn << 16) | (imm3 << 12) | imm8); + } + return; + } + case ARM_FMT_MOVW: { + int is_movt = slice_eq_cstr(mn, "movt"); + u32 rd = parse_reg(d); + RelocKind k; + ObjSymId sym; + i64 off; + expect_comma(d); + if (parse_movw_mod(d, &k, &sym, &off, is_movt)) { + MCEmitter* mc = asm_driver_mc(d); + u32 base = is_movt ? arm_movt(rd, 0) : arm_movw(rd, 0); + mc_emit_reloc_at(mc, mc->section_id, mc_pos(mc), k, sym, off, 0, 0); + emit_t32(d, base); + } else { + u32 imm16 = (u32)parse_imm(d) & 0xffffu; + emit_t32(d, is_movt ? arm_movt(rd, imm16) : arm_movw(rd, imm16)); + } + return; + } + case ARM_FMT_ADDW: { + u32 rd = parse_reg(d), rn, imm12; + expect_comma(d); + rn = parse_reg(d); + expect_comma(d); + imm12 = (u32)parse_imm(d) & 0xfffu; + emit_t32(d, slice_eq_cstr(mn, "subw") ? arm_sub_imm12(rd, rn, imm12) + : arm_add_imm12(rd, rn, imm12)); + return; + } + case ARM_FMT_SHIFT_IMM: { + u32 type = (desc->match >> 4) & 3u; /* hw2[5:4] */ + u32 rd = parse_reg(d), rm, sh; + expect_comma(d); + rm = parse_reg(d); + expect_comma(d); + sh = (u32)parse_imm(d); + emit_t32(d, arm_shift_imm(type, rd, rm, sh)); + return; + } + case ARM_FMT_MUL: { + u32 rd = parse_reg(d), rn, rm; + expect_comma(d); + rn = parse_reg(d); + expect_comma(d); + rm = parse_reg(d); + emit_t32(d, arm_mul(rd, rn, rm)); + return; + } + case ARM_FMT_MLA: { + u32 rd = parse_reg(d), rn, rm, ra; + expect_comma(d); + rn = parse_reg(d); + expect_comma(d); + rm = parse_reg(d); + expect_comma(d); + ra = parse_reg(d); + emit_t32(d, slice_eq_cstr(mn, "mls") ? arm_mls(rd, rn, rm, ra) + : arm_mla(rd, rn, rm, ra)); + return; + } + case ARM_FMT_DIV: { + u32 rd = parse_reg(d), rn, rm; + expect_comma(d); + rn = parse_reg(d); + expect_comma(d); + rm = parse_reg(d); + emit_t32(d, slice_eq_cstr(mn, "udiv") ? arm_udiv(rd, rn, rm) + : arm_sdiv(rd, rn, rm)); + return; + } + case ARM_FMT_MULL: { + u32 rdlo = parse_reg(d), rdhi, rn, rm; + expect_comma(d); + rdhi = parse_reg(d); + expect_comma(d); + rn = parse_reg(d); + expect_comma(d); + rm = parse_reg(d); + emit_t32(d, slice_eq_cstr(mn, "smull") ? arm_smull(rdlo, rdhi, rn, rm) + : arm_umull(rdlo, rdhi, rn, rm)); + return; + } + case ARM_FMT_EXT: { + u32 rd = parse_reg(d), rm; + expect_comma(d); + rm = parse_reg(d); + emit_t32(d, desc->match | (rd << 8) | rm); + return; + } + case ARM_FMT_REV: { + u32 rd = parse_reg(d), rm; + expect_comma(d); + rm = parse_reg(d); + /* match has rm in hw1[3:0] and hw2[3:0]; both equal rm for REV/CLZ. */ + emit_t32(d, (desc->match & 0xfff0fff0u) | (rm << 16) | (rd << 8) | rm); + return; + } + case ARM_FMT_BFX: { + u32 rd = parse_reg(d), rn, lsb, width; + expect_comma(d); + rn = parse_reg(d); + expect_comma(d); + lsb = (u32)parse_imm(d); + expect_comma(d); + width = (u32)parse_imm(d); + emit_t32(d, slice_eq_cstr(mn, "ubfx") ? arm_ubfx(rd, rn, lsb, width) + : arm_sbfx(rd, rn, lsb, width)); + return; + } + case ARM_FMT_BFI: { + u32 rd = parse_reg(d), rn, lsb, width; + expect_comma(d); + rn = parse_reg(d); + expect_comma(d); + lsb = (u32)parse_imm(d); + expect_comma(d); + width = (u32)parse_imm(d); + emit_t32(d, arm_bfi(rd, rn, lsb, width)); + return; + } + case ARM_FMT_BFC: { + u32 rd = parse_reg(d), lsb, width; + expect_comma(d); + lsb = (u32)parse_imm(d); + expect_comma(d); + width = (u32)parse_imm(d); + emit_t32(d, arm_bfc(rd, lsb, width)); + return; + } + case ARM_FMT_LDST_T3: { + u32 rt = parse_reg(d), base; + i64 disp; + expect_comma(d); + parse_mem(d, &base, &disp); + if (disp < 0) { + /* negative offset -> emit the T4 (±imm8) form. */ + u32 t4 = (desc->match & 0xfff00000u) - 0x00100000u; /* not used */ + (void)t4; + asm_driver_panic(d, "arm32 asm: use the T4 (#-imm8) form for negative offsets"); + } + emit_t32(d, (desc->match & 0xfff00000u) | (base << 16) | (rt << 12) | + ((u32)disp & 0xfffu)); + return; + } + case ARM_FMT_LDST_T4: { + u32 rt = parse_reg(d), base; + i64 disp; + u32 add, imm8; + expect_comma(d); + parse_mem(d, &base, &disp); + add = disp >= 0 ? 1u : 0u; + imm8 = (u32)(disp < 0 ? -disp : disp) & 0xffu; + emit_t32(d, (desc->match & 0xfff00000u) | (base << 16) | (rt << 12) | + 0xc00u | (add << 9) | imm8); + return; + } + case ARM_FMT_LDREX: { + u32 rt = parse_reg(d), base; + i64 disp = 0; + expect_comma(d); + parse_mem(d, &base, &disp); + emit_t32(d, arm_ldrex(rt, base, (u32)(disp / 4))); + return; + } + case ARM_FMT_STREX: { + u32 rd = parse_reg(d), rt, base; + i64 disp = 0; + expect_comma(d); + rt = parse_reg(d); + expect_comma(d); + parse_mem(d, &base, &disp); + emit_t32(d, arm_strex(rd, rt, base, (u32)(disp / 4))); + return; + } + case ARM_FMT_PUSHPOP: { + u32 list = parse_reglist(d); + emit_t32(d, slice_eq_cstr(mn, "pop.w") ? arm_pop_w(list) + : arm_push_w(list)); + return; + } + case ARM_FMT_BARRIER: { + u32 opt = parse_barrier_opt(d); + emit_t32(d, (desc->match & 0xfffffff0u) | (opt & 0xfu)); + return; + } + case ARM_FMT_TB: { + u32 rn, rm, sh; + asm_driver_expect_punct(d, '[', "'[' in tbb/tbh operand"); + rn = parse_reg(d); + expect_comma(d); + rm = parse_reg(d); + (void)parse_opt_shift(d, &sh); /* lsl #1 for tbh — implied by mnemonic */ + asm_driver_expect_punct(d, ']', "']' in tbb/tbh operand"); + emit_t32(d, slice_eq_cstr(mn, "tbh") ? arm_tbh(rn, rm) : arm_tbb(rn, rm)); + return; + } + case ARM_FMT_BL: { + emit_t32(d, 0xf000d000u); /* placeholder */ + /* reloc rides the BL we just emitted (offset = its start). */ + { + MCEmitter* mc = asm_driver_mc(d); + ObjSymId sym = OBJ_SYM_NONE; + i64 off = 0; + u32 pos = mc_pos(mc) - 4u; + asm_driver_parse_sym_expr(d, &sym, &off); + mc_emit_reloc_at(mc, mc->section_id, pos, R_ARM_THM_CALL, sym, off, 0, + 0); + } + return; + } + case ARM_FMT_BRANCH_T4: { + emit_t32(d, 0xf0009000u); + { + MCEmitter* mc = asm_driver_mc(d); + ObjSymId sym = OBJ_SYM_NONE; + i64 off = 0; + u32 pos = mc_pos(mc) - 4u; + asm_driver_parse_sym_expr(d, &sym, &off); + mc_emit_reloc_at(mc, mc->section_id, pos, R_ARM_THM_JUMP24, sym, off, 0, + 0); + } + return; + } + case ARM_FMT_BRANCH_T3: { + emit_t32(d, 0xf0008000u | (cond << 22)); + { + MCEmitter* mc = asm_driver_mc(d); + ObjSymId sym = OBJ_SYM_NONE; + i64 off = 0; + u32 pos = mc_pos(mc) - 4u; + asm_driver_parse_sym_expr(d, &sym, &off); + mc_emit_reloc_at(mc, mc->section_id, pos, R_ARM_THM_JUMP19, sym, off, 0, + 0); + } + return; + } + case ARM_FMT_B16: + case ARM_FMT_BCC16: { + /* 16-bit branches: emit the 32-bit wide form so the relocation has the + * full range (the disassembler still round-trips the wide encoding). */ + if (cond == ARM_CC_AL) { + emit_t32(d, 0xf0009000u); + { + MCEmitter* mc = asm_driver_mc(d); + ObjSymId sym = OBJ_SYM_NONE; + i64 off = 0; + u32 pos = mc_pos(mc) - 4u; + asm_driver_parse_sym_expr(d, &sym, &off); + mc_emit_reloc_at(mc, mc->section_id, pos, R_ARM_THM_JUMP24, sym, off, + 0, 0); + } + } else { + emit_t32(d, 0xf0008000u | (cond << 22)); + { + MCEmitter* mc = asm_driver_mc(d); + ObjSymId sym = OBJ_SYM_NONE; + i64 off = 0; + u32 pos = mc_pos(mc) - 4u; + asm_driver_parse_sym_expr(d, &sym, &off); + mc_emit_reloc_at(mc, mc->section_id, pos, R_ARM_THM_JUMP19, sym, off, + 0, 0); + } + } + return; + } + case ARM_FMT_CBZ: { + u32 rn = parse_reg(d); + i64 imm; + expect_comma(d); + (void)asm_driver_eat_punct(d, '#'); + imm = asm_driver_parse_const(d); + { + /* imm is the PC-relative byte offset (must be +4..+130, even). The + * encoded imm6 is (off-4)/2 half-words from the branch. */ + i64 hw = (imm - 4) / 2; + u32 op = slice_eq_cstr(mn, "cbnz") ? 1u : 0u; + if (imm < 4 || imm > 130 || (imm & 1)) + asm_driver_panic(d, "arm32 asm: cbz/cbnz target out of range"); + emit_t16(d, arm_cbz_raw(op, rn, (u32)hw)); + } + return; + } + case ARM_FMT_IT: { + /* `it<x><y><z> cc` — the suffix letters arrive folded into the mnemonic + * (e.g. "itte"); we recover the mask from the suffix + the firstcond. */ + AsmTok t = asm_driver_next(d); + Slice ccn; + int fc; + u32 mask; + size_t nletters = mn.len - 2; /* letters after "it" */ + if (t.kind != ASM_TOK_IDENT) + asm_driver_panic(d, "arm32 asm: IT expects a condition"); + ccn = pool_slice(asm_driver_pool(d), t.v.ident); + fc = arm32_cond_from_name(ccn); + if (fc < 0) asm_driver_panic(d, "arm32 asm: bad IT condition"); + /* Build the mask: bit3=1; for each of the up-to-3 suffix letters, bit + * (3-k) is set to firstcond[0] for 't' (then) or its inverse for 'e'. */ + mask = 0x8u; + { + u32 then = (u32)fc & 1u; + for (size_t k = 0; k < nletters && k < 3; ++k) { + u32 bit = (mn.s[2 + k] == 't') ? then : (then ^ 1u); + mask |= (bit << (3u - (u32)(k + 1))); + } + /* The lowest set bit position marks the block end. */ + mask |= (1u << (3u - (u32)nletters)); + } + emit_t16(d, arm_it((u32)fc, mask)); + return; + } + case ARM_FMT_MOVHI16: { + u32 rd = parse_reg(d), rm; + expect_comma(d); + rm = parse_reg(d); + emit_t16(d, arm_mov_hi(rd, rm)); + return; + } + case ARM_FMT_BX: { + u32 rm = parse_reg(d); + emit_t16(d, slice_eq_cstr(mn, "blx") ? arm_blx_reg(rm) : arm_bx(rm)); + return; + } + case ARM_FMT_BKPT: { + u32 imm8 = (u32)parse_imm(d) & 0xffu; + emit_t16(d, arm_bkpt(imm8)); + return; + } + case ARM_FMT_EXT16: { + u32 rd = parse_reg(d), rm; + expect_comma(d); + rm = parse_reg(d); + emit_t16(d, (u16)((desc->match & 0xffc0u) | ((rm & 7u) << 3) | (rd & 7u))); + return; + } + case ARM_FMT_DPI8_16: { + u32 rd = parse_reg(d), imm8; + expect_comma(d); + imm8 = (u32)parse_imm(d) & 0xffu; + emit_t16(d, (u16)((desc->match & 0xf800u) | ((rd & 7u) << 8) | imm8)); + return; + } + case ARM_FMT_ADDSUB3_16: { + u32 rd = parse_reg(d), rn, imm3; + expect_comma(d); + rn = parse_reg(d); + expect_comma(d); + imm3 = (u32)parse_imm(d) & 7u; + emit_t16(d, (u16)((desc->match & 0xfe00u) | (imm3 << 6) | ((rn & 7u) << 3) | + (rd & 7u))); + return; + } + case ARM_FMT_ADDSUBR_16: { + u32 rd = parse_reg(d), rn, rm; + expect_comma(d); + rn = parse_reg(d); + expect_comma(d); + rm = parse_reg(d); + emit_t16(d, (u16)((desc->match & 0xfe00u) | ((rm & 7u) << 6) | + ((rn & 7u) << 3) | (rd & 7u))); + return; + } + case ARM_FMT_SHIFTI_16: { + u32 rd = parse_reg(d), rm, imm5; + expect_comma(d); + rm = parse_reg(d); + expect_comma(d); + imm5 = (u32)parse_imm(d) & 0x1fu; + emit_t16(d, (u16)((desc->match & 0xf800u) | (imm5 << 6) | ((rm & 7u) << 3) | + (rd & 7u))); + return; + } + case ARM_FMT_ALU_16: { + u32 rdn = parse_reg(d), rm; + expect_comma(d); + rm = parse_reg(d); + if (slice_eq_cstr(mn, "muls")) { + /* muls rdm, rn, rdm — skip the (redundant) third operand if present. */ + if (asm_driver_eat_comma(d)) (void)parse_reg(d); + } + emit_t16(d, (u16)((desc->match & 0xffc0u) | ((rm & 7u) << 3) | (rdn & 7u))); + return; + } + case ARM_FMT_HIREG_16: { + u32 rdn = parse_reg(d), rm; + expect_comma(d); + rm = parse_reg(d); + emit_t16(d, (u16)((desc->match & 0xff00u) | (((rdn >> 3) & 1u) << 7) | + ((rm & 0xfu) << 3) | (rdn & 7u))); + return; + } + case ARM_FMT_LDSTI5_16: { + u32 rt = parse_reg(d), base; + i64 disp; + u32 op, scale, imm5; + expect_comma(d); + parse_mem(d, &base, &disp); + op = (desc->match >> 11) & 0x1fu; + scale = (op <= 0x0du) ? 4u : (op <= 0x0fu) ? 1u : 2u; + imm5 = ((u32)disp / scale) & 0x1fu; + emit_t16(d, (u16)((desc->match & 0xf800u) | (imm5 << 6) | + ((base & 7u) << 3) | (rt & 7u))); + return; + } + case ARM_FMT_LDSTSP_16: { + u32 rt = parse_reg(d), base; + i64 disp; + expect_comma(d); + parse_mem(d, &base, &disp); + emit_t16(d, (u16)((desc->match & 0xf800u) | ((rt & 7u) << 8) | + (((u32)disp / 4u) & 0xffu))); + return; + } + case ARM_FMT_ADDSP_16: { + u32 rd = parse_reg(d), imm8; + expect_comma(d); + (void)parse_reg(d); /* sp/pc — implied by the mnemonic/encoding */ + expect_comma(d); + imm8 = ((u32)parse_imm(d) / 4u) & 0xffu; + emit_t16(d, (u16)((desc->match & 0xf800u) | ((rd & 7u) << 8) | imm8)); + return; + } + case ARM_FMT_ADJSP_16: { + u32 imm7; + (void)parse_reg(d); /* sp */ + expect_comma(d); + (void)parse_reg(d); /* sp */ + expect_comma(d); + imm7 = ((u32)parse_imm(d) / 4u) & 0x7fu; + emit_t16(d, (u16)((desc->match & 0xff80u) | imm7)); + return; + } + case ARM_FMT_PUSHPOP_16: { + u32 list = parse_reglist(d); + u32 lo = list & 0xffu; + u32 extra = slice_eq_cstr(mn, "pop") ? ((list >> 15) & 1u) + : ((list >> 14) & 1u); + emit_t16(d, (u16)((desc->match & 0xfe00u) | (extra << 8) | lo)); + return; + } + } + asm_driver_panic(d, "arm32 asm: unhandled instruction format"); +} + +static void arm32_arch_asm_insn(ArchAsm* base, AsmDriver* d, Sym mnemonic) { + const Arm32InsnDesc* desc; + u32 cond = ARM_CC_AL; + (void)base; + (void)asm_driver_cur_section(d); /* ensure .text exists */ + desc = resolve_mnemonic(d, pool_slice(asm_driver_pool(d), mnemonic), &cond); + assemble_one(d, desc, cond); +} + +static void arm32_arch_asm_destroy(ArchAsm* base) { (void)base; } + +/* ---- textual-assembly operand syntax (printer <-> parser seam) ---- + * Inverse of the `.s` parsers above. ARM uses the GNU-as `:lower16:`/`:upper16:` + * prefix form for MOVW/MOVT symbol halves and bare numeric targets for branches + * (the symbolizer synthesizes labels for the local-branch set). */ +static int arm32_reloc_operand(u16 kind, KitObjFmt fmt, ArchRelocOperand* out) { + (void)fmt; + out->prefix = ""; + out->suffix = ""; + out->addend_bias = 0; + out->emit_anchor = 0; + out->ref_anchor = 0; + switch (kind) { + case R_ARM_THM_MOVW_ABS_NC: + out->surg = ARCH_RELOC_SURG_TAIL; + out->prefix = "#:lower16:"; + return 1; + case R_ARM_THM_MOVT_ABS: + out->surg = ARCH_RELOC_SURG_TAIL; + out->prefix = "#:upper16:"; + return 1; + case R_ARM_THM_CALL: + case R_ARM_THM_JUMP24: + case R_ARM_THM_JUMP19: + out->surg = ARCH_RELOC_SURG_TAIL; + return 1; + default: + return 0; /* R_ABS32 / R_REL32 / TLS -> keep numeric */ + } +} + +/* Intra-section local branches whose target the disassembler renders + * numerically; cc -S synthesizes a label there. Calls (bl) are excluded. */ +static int arm32_is_local_branch(KitSlice m) { + if (m.len >= 1 && m.s[0] == 'b') { + if (slice_eq_cstr(m, "b") || slice_eq_cstr(m, "b.w")) return 1; + if (slice_eq_cstr(m, "cbz") || slice_eq_cstr(m, "cbnz")) return 1; + /* b<cond> / b<cond>.w */ + { + size_t base = m.len; + if (m.len >= 3 && m.s[m.len - 2] == '.' && m.s[m.len - 1] == 'w') + base = m.len - 2; + if (base == 3) { + if (arm32_cond_from_name(arm_slice(m.s + 1, 2)) >= 0) return 1; + } + } + } + return 0; +} + +const ArchAsmOps arm32_asm_ops = { + .reloc_operand = arm32_reloc_operand, + .is_local_branch = arm32_is_local_branch, +}; + ArchAsm* arm32_arch_asm_new(Compiler* c) { - (void)c; - return NULL; + Arm32Asm* a = arena_new(c->tu, Arm32Asm); + memset(a, 0, sizeof *a); + a->base.insn = arm32_arch_asm_insn; + a->base.destroy = arm32_arch_asm_destroy; + a->c = c; + return &a->base; } diff --git a/src/arch/arm32/disasm.c b/src/arch/arm32/disasm.c @@ -1,13 +1,456 @@ -/* ARM32 (Thumb-2) disassembler. +/* ARM32 (Thumb-2) disassembler — descriptor-table driven. * - * Phase 1 (the walking skeleton) ships no descriptor-driven decode — the - * codegen gate is run-correctness under qemu, not byte-golden disassembly. - * Table-driven decode (the 16/32-bit Thumb-2 families + IT-state tracking) - * is a Phase-2 op-group deliverable; until then arm32_disasm_new yields no - * disassembler (objdump/disas report unsupported for arm32). */ -#include "arch/arch.h" + * Decodes a Thumb-2 instruction by reading the first half-word, picking the + * width (the Thumb-2 rule: hw1[15:11] in {0b11101, 0b11110, 0b11111} => 32-bit; + * else 16-bit), composing the decode word ((hw1<<16)|hw2 for 32-bit, hw in the + * low 16 bits for 16-bit), and linear-scanning arm32_insn_table for the first + * matching row. The matched format drives operand printing (arm32_print_ + * operands). Conditional branches (B<cond>.W T3, B<cond> T1) and the IT block + * carry their condition in the mnemonic suffix. + * + * IT-state: an `IT{x{y{z}}} cc` opens a block of up to 4 instructions whose + * condition is rendered as a mnemonic suffix (e.g. `moveq`). The block formatter + * tracks the remaining count + per-slot condition across a decode_block so the + * conditional renders show. A standalone decode_one (no preceding IT) renders + * the bare mnemonic. + * + * Unknown words fall back to `.inst <hex>` (32-bit) / `.hword <hex>` (16-bit). */ -ArchDisasm* arm32_disasm_new(Compiler* c) { +#include "arch/arm32/disasm.h" + +#include <string.h> + +#include "arch/arm32/isa.h" +#include "core/heap.h" +#include "core/strbuf.h" + +#define ARM32_DASM_MNEM_CAP 24u +#define ARM32_DASM_OPS_CAP 96u +#define ARM32_DASM_ANN_CAP 32u +#define ARM32_ENCODING_UNKNOWN 0xffffffffu + +typedef struct Arm32InsnFormatter { + ArchInsnFormatter base; + Compiler* c; + Heap* heap; + /* IT-block state threaded across a decode_block render: `it_count` slots + * remain, each conditional on `it_cond[slot]`. Reset by decode_one. */ + u32 it_count; + u8 it_cond[4]; + char mnem_buf[ARM32_DASM_MNEM_CAP]; + char ops_buf[ARM32_DASM_OPS_CAP]; + char ann_buf[ARM32_DASM_ANN_CAP]; + StrBuf mnem; + StrBuf ops; + StrBuf ann; +} Arm32InsnFormatter; + +typedef struct Arm32Disasm { + ArchDisasm base; + Arm32InsnFormatter fmt; +} Arm32Disasm; + +static KitStatus arm32_format_insn(ArchInsnFormatter*, const KitDecodedInsn*, + KitInsn*); +static void arm32_formatter_destroy(ArchInsnFormatter*); + +static u32 arm_read_u16_le(const u8* b) { return (u32)b[0] | ((u32)b[1] << 8); } + +/* The Thumb-2 32-bit-instruction rule: the first half-word's bits[15:11] are + * one of 0b11101 / 0b11110 / 0b11111. */ +static int arm_is_32bit(u32 hw1) { + u32 top5 = (hw1 >> 11) & 0x1fu; + return top5 == 0x1du || top5 == 0x1eu || top5 == 0x1fu; +} + +/* ---- operand-decode helpers (KitDecodedInsn.operands) ---- */ +static void arm_decop_none(KitDecodedOperand* o) { + memset(o, 0, sizeof(*o)); + o->kind = KIT_DECOP_NONE; + o->index_reg = REG_NONE; +} +static void arm_decop_reg(KitDecodedOperand* o, u32 reg) { + arm_decop_none(o); + o->kind = KIT_DECOP_REG; + o->width_bits = 32; + o->reg = reg; +} +static void arm_decop_imm(KitDecodedOperand* o, i64 imm) { + arm_decop_none(o); + o->kind = KIT_DECOP_IMM; + o->imm = imm; +} +static void arm_decop_mem(KitDecodedOperand* o, u32 base, i64 imm) { + arm_decop_none(o); + o->kind = KIT_DECOP_MEM; + o->width_bits = 32; + o->reg = base; + o->imm = imm; +} +static void arm_decop_pcrel(KitDecodedOperand* o, u64 pc, i64 disp) { + arm_decop_none(o); + o->kind = KIT_DECOP_PCREL; + o->imm = (i64)(pc + (u64)disp); +} + +/* Stable opcode id for an emulator/decode consumer (small set). */ +static u32 arm32_semantic_opcode(const Arm32InsnDesc* d) { + if (!d) return ARM32_DEC_UNKNOWN; + switch ((Arm32Format)d->fmt) { + case ARM_FMT_BL: + return ARM32_DEC_BL; + case ARM_FMT_BX: + return slice_eq_cstr(d->mnemonic, "blx") ? ARM32_DEC_BLX : ARM32_DEC_BX; + case ARM_FMT_BRANCH_T4: + case ARM_FMT_B16: + return ARM32_DEC_B; + case ARM_FMT_BRANCH_T3: + case ARM_FMT_BCC16: + case ARM_FMT_CBZ: + return ARM32_DEC_BCOND; + case ARM_FMT_BKPT: + return ARM32_DEC_BKPT; + case ARM_FMT_IT: + return ARM32_DEC_IT; + default: + return ARM32_DEC_UNKNOWN; + } +} + +static u16 arm32_decode_flags(const Arm32InsnDesc* d, u32 word) { + u16 flags = 0; + if (!d) return 0; + switch ((Arm32Format)d->fmt) { + case ARM_FMT_BL: + flags |= KIT_DECODE_BRANCH | KIT_DECODE_CALL; + break; + case ARM_FMT_BX: + flags |= KIT_DECODE_TERMINATOR | KIT_DECODE_BRANCH; + if (slice_eq_cstr(d->mnemonic, "blx")) + flags |= KIT_DECODE_CALL; + else if (((word >> 3) & 0xfu) == ARM_LR) + flags |= KIT_DECODE_RET; /* bx lr */ + break; + case ARM_FMT_BRANCH_T4: + case ARM_FMT_B16: + flags |= KIT_DECODE_TERMINATOR | KIT_DECODE_BRANCH; + break; + case ARM_FMT_BRANCH_T3: + case ARM_FMT_BCC16: + case ARM_FMT_CBZ: + flags |= KIT_DECODE_BRANCH; + break; + case ARM_FMT_TB: + flags |= KIT_DECODE_TERMINATOR | KIT_DECODE_BRANCH | KIT_DECODE_MEMORY; + break; + case ARM_FMT_BKPT: + flags |= KIT_DECODE_TERMINATOR | KIT_DECODE_TRAP; + break; + case ARM_FMT_LDST_T3: + case ARM_FMT_LDST_T4: + case ARM_FMT_LDREX: + case ARM_FMT_STREX: + case ARM_FMT_PUSHPOP: + flags |= KIT_DECODE_MEMORY; + break; + default: + break; + } + return flags; +} + +/* Fill KitDecodedInsn.operands from the matched descriptor + word — a coarse + * structured view for emu/decode consumers (the textual render goes through + * arm32_print_operands separately). */ +static void arm32_decode_operands(const Arm32InsnDesc* d, u32 w, u64 pc, + KitDecodedInsn* out) { + if (!d) return; + switch ((Arm32Format)d->fmt) { + case ARM_FMT_DP_REG: + case ARM_FMT_SHIFT_REG: + case ARM_FMT_MUL: + case ARM_FMT_DIV: { + out->noperands = 3; + arm_decop_reg(&out->operands[0], (w >> 8) & 0xfu); + arm_decop_reg(&out->operands[1], (w >> 16) & 0xfu); + arm_decop_reg(&out->operands[2], w & 0xfu); + break; + } + case ARM_FMT_MOVW: { + u32 imm4 = (w >> 16) & 0xfu, i = (w >> 26) & 1u; + u32 imm3 = (w >> 12) & 7u, imm8 = w & 0xffu; + out->noperands = 2; + arm_decop_reg(&out->operands[0], (w >> 8) & 0xfu); + arm_decop_imm(&out->operands[1], + (i64)((imm4 << 12) | (i << 11) | (imm3 << 8) | imm8)); + break; + } + case ARM_FMT_LDST_T3: { + out->noperands = 2; + arm_decop_reg(&out->operands[0], (w >> 12) & 0xfu); + arm_decop_mem(&out->operands[1], (w >> 16) & 0xfu, (i64)(w & 0xfffu)); + break; + } + case ARM_FMT_BL: + case ARM_FMT_BRANCH_T4: { + out->noperands = 1; + (void)w; /* offset re-decoded by the printer */ + arm_decop_pcrel(&out->operands[0], pc, 0); + break; + } + default: + break; + } +} + +static KitStatus arm32_decode_one(Compiler* c, const u8* bytes, size_t len, + u64 pc, KitDecodedInsn* out) { + const Arm32InsnDesc* desc; + u32 hw1, word; + int is16; (void)c; - return NULL; + if (!bytes || !out) return KIT_INVALID; + if (len < 2u) return KIT_MALFORMED; + memset(out, 0, sizeof(*out)); + for (u32 i = 0; i < KIT_DECODE_MAX_OPERANDS; ++i) + arm_decop_none(&out->operands[i]); + + hw1 = arm_read_u16_le(bytes); + is16 = !arm_is_32bit(hw1); + if (is16) { + word = hw1; + out->nbytes = 2; + } else { + if (len < 4u) return KIT_MALFORMED; + word = (hw1 << 16) | arm_read_u16_le(bytes + 2); + out->nbytes = 4; + } + desc = arm32_disasm_find(word, is16); + + out->pc = pc; + out->bytes = bytes; + out->encoding_id = + desc ? (u32)(desc - arm32_insn_table) : ARM32_ENCODING_UNKNOWN; + out->opcode = arm32_semantic_opcode(desc); + out->flags = arm32_decode_flags(desc, word); + out->arch[0] = word; + out->arch[1] = desc ? desc->fmt : 0xffu; + arm32_decode_operands(desc, word, pc, out); + return KIT_OK; +} + +static KitStatus arm32_decode_block(Compiler* c, const u8* bytes, size_t len, + u64 pc, KitDecodedInsn* out, u32 cap, + u32* n_out) { + u32 n = 0; + if (n_out) *n_out = 0; + if (!bytes || !out || !n_out) return KIT_INVALID; + while (n < cap && len > 0) { + KitStatus st = arm32_decode_one(c, bytes, len, pc, &out[n]); + if (st != KIT_OK) return n ? KIT_OK : st; + bytes += out[n].nbytes; + len -= out[n].nbytes; + pc += out[n].nbytes; + ++n; + if (out[n - 1u].flags & KIT_DECODE_TERMINATOR) break; + } + *n_out = n; + return KIT_OK; +} + +static void arm32_formatter_init(Arm32InsnFormatter* f, Compiler* c, Heap* h) { + memset(f, 0, sizeof(*f)); + f->c = c; + f->heap = h; + f->base.format = arm32_format_insn; + f->base.destroy = arm32_formatter_destroy; + strbuf_init(&f->mnem, f->mnem_buf, sizeof f->mnem_buf); + strbuf_init(&f->ops, f->ops_buf, sizeof f->ops_buf); + strbuf_init(&f->ann, f->ann_buf, sizeof f->ann_buf); +} + +/* Expand an IT mask into the per-slot then/else conditions. firstcond is the + * condition of slot 0; subsequent slots use firstcond or its inverse per the + * mask's T/E bits. Returns the block length (1..4). */ +static u32 arm_it_expand(u32 firstcond, u32 mask, u8 cond_out[4]) { + u32 n, k; + /* Block length = 4 - (position of the lowest set bit in the 4-bit mask). + * mask bit3 is always 1 for a valid IT. */ + if (mask & 1u) + n = 4; + else if (mask & 2u) + n = 3; + else if (mask & 4u) + n = 2; + else + n = 1; + cond_out[0] = (u8)(firstcond & 0xfu); + for (k = 1; k < n; ++k) { + /* bit (4-k) of mask: when it equals firstcond[0], slot k is the "then" + * condition (== firstcond), else the inverse. */ + u32 bit = (mask >> (4u - k)) & 1u; + u32 then = (firstcond & 1u); + cond_out[k] = (u8)((bit == then) ? firstcond : (firstcond ^ 1u)); + } + for (; k < 4; ++k) cond_out[k] = (u8)(firstcond & 0xfu); + return n; +} + +/* Build the rendered mnemonic into f->mnem: base mnemonic (keeping the `.w` + * width suffix) plus a condition suffix when the instruction is inside an IT + * block or is itself a conditional branch (T3 / 16-bit Bcc). The IT mnemonic + * itself renders as `it<x><y><z>` with the firstcond as its sole operand. */ +static void arm32_render_mnemonic(Arm32InsnFormatter* f, const Arm32InsnDesc* d, + u32 word, u32 it_cond, int in_it) { + Slice mn = d->mnemonic; + strbuf_reset(&f->mnem); + if ((Arm32Format)d->fmt == ARM_FMT_BRANCH_T3) { + /* b.w -> b<cc>.w */ + strbuf_putc(&f->mnem, 'b'); + strbuf_puts(&f->mnem, arm32_cond_name(arm32_branch_t3_cond(word))); + strbuf_puts(&f->mnem, ".w"); + return; + } + if ((Arm32Format)d->fmt == ARM_FMT_BCC16) { + strbuf_putc(&f->mnem, 'b'); + strbuf_puts(&f->mnem, arm32_cond_name((word >> 8) & 0xfu)); + return; + } + if ((Arm32Format)d->fmt == ARM_FMT_IT) { + u32 firstcond = (word >> 4) & 0xfu; + u32 mask = word & 0xfu; + u8 cc[4]; + u32 nblk = arm_it_expand(firstcond, mask, cc); + u32 k; + strbuf_puts(&f->mnem, "it"); + for (k = 1; k < nblk; ++k) + strbuf_putc(&f->mnem, (cc[k] == cc[0]) ? 't' : 'e'); + return; + } + /* Base mnemonic, with optional IT condition suffix inserted before ".w". */ + if (in_it) { + const char* ccn = arm32_cond_name(it_cond); + int is16 = (d->flags & ARM_FMT_W16) != 0; + /* In an IT block the 16-bit flag-setting forms drop the implicit trailing + * `s` (ARM unified syntax: predication implies it), e.g. movs -> moveq. The + * exceptions are mnemonics whose `s` is part of the name, not a flag suffix + * (none in the 16-bit DP set this disassembler emits). */ + if (is16 && mn.len >= 2 && mn.s[mn.len - 1] == 's') { + strbuf_putn(&f->mnem, mn.s, mn.len - 1); + strbuf_puts(&f->mnem, ccn); + } else if (mn.len >= 2 && mn.s[mn.len - 2] == '.' && mn.s[mn.len - 1] == 'w') { + strbuf_putn(&f->mnem, mn.s, mn.len - 2); + strbuf_puts(&f->mnem, ccn); + strbuf_puts(&f->mnem, ".w"); + } else { + strbuf_put_slice(&f->mnem, mn); + strbuf_puts(&f->mnem, ccn); + } + return; + } + strbuf_put_slice(&f->mnem, mn); +} + +static KitStatus arm32_format_insn(ArchInsnFormatter* base, + const KitDecodedInsn* insn, KitInsn* out) { + Arm32InsnFormatter* f = (Arm32InsnFormatter*)base; + const Arm32InsnDesc* desc; + u32 word; + int is16; + u32 it_cond = ARM_CC_AL; + int in_it = 0; + if (!f || !insn || !out) return KIT_INVALID; + word = (u32)insn->arch[0]; + is16 = insn->nbytes == 2u; + desc = arm32_disasm_find(word, is16); + + /* Consume one IT slot if a block is active (and this insn is not the IT). */ + if (f->it_count > 0 && (!desc || (Arm32Format)desc->fmt != ARM_FMT_IT)) { + it_cond = f->it_cond[0]; + in_it = 1; + f->it_cond[0] = f->it_cond[1]; + f->it_cond[1] = f->it_cond[2]; + f->it_cond[2] = f->it_cond[3]; + f->it_count--; + } + + if (desc) { + arm32_render_mnemonic(f, desc, word, it_cond, in_it); + strbuf_reset(&f->ops); + arm32_print_operands(&f->ops, desc, word, insn->pc); + /* The IT instruction prints its firstcond and opens a block whose per-slot + * conditions the following instructions consume. */ + if ((Arm32Format)desc->fmt == ARM_FMT_IT) { + u32 firstcond = (word >> 4) & 0xfu; + u32 mask = word & 0xfu; + strbuf_puts(&f->ops, arm32_cond_name(firstcond)); + f->it_count = arm_it_expand(firstcond, mask, f->it_cond); + } + } else { + strbuf_reset(&f->mnem); + strbuf_puts(&f->mnem, is16 ? ".hword" : ".inst"); + strbuf_reset(&f->ops); + strbuf_put_hex_u64(&f->ops, (u64)word); + } + + strbuf_reset(&f->ann); + out->vaddr = insn->pc; + out->bytes = insn->bytes; + out->nbytes = insn->nbytes; + out->mnemonic = strbuf_slice(&f->mnem); + out->operands = strbuf_slice(&f->ops); + out->annotation = strbuf_slice(&f->ann); + return KIT_OK; +} + +static void arm32_formatter_destroy(ArchInsnFormatter* base) { + Arm32InsnFormatter* f = (Arm32InsnFormatter*)base; + if (!f) return; + f->heap->free(f->heap, f, sizeof(*f)); +} + +static ArchInsnFormatter* arm32_formatter_new(Compiler* c) { + Heap* h = (Heap*)c->ctx->heap; + Arm32InsnFormatter* f = + (Arm32InsnFormatter*)h->alloc(h, sizeof(*f), _Alignof(Arm32InsnFormatter)); + if (!f) return NULL; + arm32_formatter_init(f, c, h); + return &f->base; +} + +static u32 arm_decode(ArchDisasm* base, const u8* bytes, size_t len, u64 vaddr, + KitInsn* out) { + Arm32Disasm* d = (Arm32Disasm*)base; + KitDecodedInsn insn; + KitStatus st = arm32_decode_one(d->fmt.c, bytes, len, vaddr, &insn); + if (st != KIT_OK) return 0; + st = arm32_format_insn(&d->fmt.base, &insn, out); + if (st != KIT_OK) return 0; + return insn.nbytes; } + +static void arm32_destroy(ArchDisasm* base) { + Arm32Disasm* d = (Arm32Disasm*)base; + d->fmt.heap->free(d->fmt.heap, d, sizeof(*d)); +} + +ArchDisasm* arm32_disasm_new(Compiler* c) { + Heap* h = (Heap*)c->ctx->heap; + Arm32Disasm* d = (Arm32Disasm*)h->alloc(h, sizeof(*d), _Alignof(Arm32Disasm)); + if (!d) return NULL; + memset(d, 0, sizeof(*d)); + d->base.decode = arm_decode; + d->base.destroy = arm32_destroy; + arm32_formatter_init(&d->fmt, c, h); + return &d->base; +} + +const ArchDecodeOps arm32_decode_ops = { + .min_insn_len = 2, + .max_insn_len = 4, + .decode_one = arm32_decode_one, + .decode_block = arm32_decode_block, + .formatter_new = arm32_formatter_new, + .format = arm32_format_insn, + .formatter_destroy = arm32_formatter_destroy, +}; diff --git a/src/arch/arm32/disasm.h b/src/arch/arm32/disasm.h @@ -0,0 +1,17 @@ +#ifndef KIT_ARCH_ARM32_DISASM_H +#define KIT_ARCH_ARM32_DISASM_H + +/* ARM32 (Thumb-2) disassembler -- ArchDisasm + ArchDecodeOps implementation. + * + * Table-driven decode over arm32_insn_table (src/arch/arm32/isa.c). Width is + * picked per the Thumb-2 rule: the first half-word's bits[15:11] in + * {0b11101, 0b11110, 0b11111} mark a 32-bit instruction (read a second + * half-word); everything else is 16-bit. Unknown words fall back to + * `.inst`/`.hword` placeholders. */ + +#include "arch/arch.h" + +ArchDisasm* arm32_disasm_new(Compiler*); +extern const ArchDecodeOps arm32_decode_ops; + +#endif diff --git a/src/arch/arm32/isa.c b/src/arch/arm32/isa.c @@ -0,0 +1,1109 @@ +/* ARM32 (Thumb-2) instruction descriptor table + operand print dispatch. + * + * Mirrors the rv64_isa.c / aa64_isa.c pattern. Each row records (mnemonic, + * match, mask, format, flags); arm32_disasm_find returns the first row whose + * masked bits match the word (first-match wins, so aliases use tighter masks + * placed before the canonical row), and arm32_print_operands renders the + * operand text from the format's field layout. + * + * Thumb-2 has two widths. A 32-bit instruction is decoded as the single value + * (hw1 << 16) | hw2 (hw1 = first half-word in memory); a 16-bit instruction is + * decoded in the low 16 bits. The descriptor's ARM_FMT_W16 flag tags 16-bit + * rows, which arm32_disasm_find matches against the low-16-bit word; 32-bit + * rows match the full word. The caller (disasm.c) determines the width from the + * Thumb-2 rule (first half-word bits[15:11] in {11101,11110,11111} => 32-bit) + * and presents the appropriate word + is16 flag. */ + +#include "arch/arm32/isa.h" + +#include <string.h> + +#include "core/slice.h" +#include "core/strbuf.h" + +/* Mnemonic Slice literal for a static table row (compile-time length). */ +#define MN(s) {{(s)}, sizeof(s) - 1} + +/* ---- 32-bit match/mask helpers ---- + * A 32-bit Thumb-2 word is (hw1 << 16) | hw2. We pin the bits that select the + * instruction (opcode + family selectors) in `match` and clear the operand + * fields in `mask`. Helper macros keep the common families readable. */ + +/* Data-processing (modified-immediate, hw1=0xF0xx): op4 in word bits 24:21 + * (hw1[8:5]), S in word bit 20 (hw1[4]), `i` in word bit 26 (free). Mask pins + * op(31:27)=11110 (0xF8000000), op4 (0x01E00000), S (0x00100000), and hw2[15] + * (0x8000); it leaves rn(19:16), i(26), imm3(14:12), rd(11:8), imm8(7:0) free. */ +#define M32_DPIMM(op4, s) \ + ((0xf000u << 16) | (((u32)(op4) & 0xfu) << 21) | (((u32)(s) & 1u) << 20)) +#define MK_DPIMM (0xfbf08000u) + +/* Data-processing (shifted register, hw1=0xEAxx): op4 in word bits 24:21, S in + * word bit 20. Mask pins op(31:25)=1110101 (0xFE000000), op4 (0x01E00000), S + * (0x00100000), hw2[15] (0x8000); clears rn, imm3/imm2/type shift, rd, rm. */ +#define M32_DPREG(op4, s) \ + ((0xea00u << 16) | (((u32)(op4) & 0xfu) << 21) | (((u32)(s) & 1u) << 20)) +#define MK_DPREG (0xfff08000u) + +const Arm32InsnDesc arm32_insn_table[] = { + /* ================================================================= + * 32-bit data processing — modified immediate (hw1 = 0xF0xx). + * MOV/MVN use rn=1111 (own formats so the rn operand is suppressed); + * CMP/CMN/TST/TEQ use rd=1111 (S=1, CMP_IMM format). Aliases come + * first (tighter mask). + * ================================================================= */ + {MN("mov.w"), M32_DPIMM(2u, 0u) | (0xfu << 16), MK_DPIMM | (0xfu << 16), + ARM_FMT_MOV_IMM, ARM_ASMFL_ALIAS, {0}}, + {MN("movs.w"), M32_DPIMM(2u, 1u) | (0xfu << 16), MK_DPIMM | (0xfu << 16), + ARM_FMT_MOV_IMM, ARM_ASMFL_ALIAS, {0}}, + {MN("mvn.w"), M32_DPIMM(3u, 0u) | (0xfu << 16), MK_DPIMM | (0xfu << 16), + ARM_FMT_MOV_IMM, ARM_ASMFL_ALIAS, {0}}, + {MN("cmn.w"), M32_DPIMM(8u, 1u) | (0xfu << 8), MK_DPIMM | (0xfu << 8), + ARM_FMT_CMP_IMM, ARM_ASMFL_ALIAS, {0}}, + {MN("cmp.w"), M32_DPIMM(13u, 1u) | (0xfu << 8), MK_DPIMM | (0xfu << 8), + ARM_FMT_CMP_IMM, ARM_ASMFL_ALIAS, {0}}, + {MN("tst.w"), M32_DPIMM(0u, 1u) | (0xfu << 8), MK_DPIMM | (0xfu << 8), + ARM_FMT_CMP_IMM, ARM_ASMFL_ALIAS, {0}}, + {MN("teq.w"), M32_DPIMM(4u, 1u) | (0xfu << 8), MK_DPIMM | (0xfu << 8), + ARM_FMT_CMP_IMM, ARM_ASMFL_ALIAS, {0}}, + {MN("and.w"), M32_DPIMM(0u, 0u), MK_DPIMM, ARM_FMT_DP_IMM, 0, {0}}, + {MN("ands.w"), M32_DPIMM(0u, 1u), MK_DPIMM, ARM_FMT_DP_IMM, 0, {0}}, + {MN("bic.w"), M32_DPIMM(1u, 0u), MK_DPIMM, ARM_FMT_DP_IMM, 0, {0}}, + {MN("orr.w"), M32_DPIMM(2u, 0u), MK_DPIMM, ARM_FMT_DP_IMM, 0, {0}}, + {MN("orn.w"), M32_DPIMM(3u, 0u), MK_DPIMM, ARM_FMT_DP_IMM, 0, {0}}, + {MN("eor.w"), M32_DPIMM(4u, 0u), MK_DPIMM, ARM_FMT_DP_IMM, 0, {0}}, + {MN("add.w"), M32_DPIMM(8u, 0u), MK_DPIMM, ARM_FMT_DP_IMM, 0, {0}}, + {MN("adds.w"), M32_DPIMM(8u, 1u), MK_DPIMM, ARM_FMT_DP_IMM, 0, {0}}, + {MN("adc.w"), M32_DPIMM(10u, 0u), MK_DPIMM, ARM_FMT_DP_IMM, 0, {0}}, + {MN("sbc.w"), M32_DPIMM(11u, 0u), MK_DPIMM, ARM_FMT_DP_IMM, 0, {0}}, + {MN("sub.w"), M32_DPIMM(13u, 0u), MK_DPIMM, ARM_FMT_DP_IMM, 0, {0}}, + {MN("subs.w"), M32_DPIMM(13u, 1u), MK_DPIMM, ARM_FMT_DP_IMM, 0, {0}}, + {MN("rsb.w"), M32_DPIMM(14u, 0u), MK_DPIMM, ARM_FMT_DP_IMM, 0, {0}}, + + /* ================================================================= + * 32-bit data processing — shifted register (hw1 = 0xEAxx). + * MOV.W rd, rm = ORR rn=1111; MVN.W = ORN rn=1111 (own formats); + * CMP/CMN/TST/TEQ use rd=1111. + * ================================================================= */ + {MN("mov.w"), M32_DPREG(2u, 0u) | (0xfu << 16), MK_DPREG | (0xfu << 16), + ARM_FMT_MOV_REG, ARM_ASMFL_ALIAS, {0}}, + {MN("mvn.w"), M32_DPREG(3u, 0u) | (0xfu << 16), MK_DPREG | (0xfu << 16), + ARM_FMT_MOV_REG, ARM_ASMFL_ALIAS, {0}}, + {MN("cmn.w"), M32_DPREG(8u, 1u) | (0xfu << 8), MK_DPREG | (0xfu << 8), + ARM_FMT_CMP_REG, ARM_ASMFL_ALIAS, {0}}, + {MN("cmp.w"), M32_DPREG(13u, 1u) | (0xfu << 8), MK_DPREG | (0xfu << 8), + ARM_FMT_CMP_REG, ARM_ASMFL_ALIAS, {0}}, + {MN("tst.w"), M32_DPREG(0u, 1u) | (0xfu << 8), MK_DPREG | (0xfu << 8), + ARM_FMT_CMP_REG, ARM_ASMFL_ALIAS, {0}}, + {MN("teq.w"), M32_DPREG(4u, 1u) | (0xfu << 8), MK_DPREG | (0xfu << 8), + ARM_FMT_CMP_REG, ARM_ASMFL_ALIAS, {0}}, + {MN("and.w"), M32_DPREG(0u, 0u), MK_DPREG, ARM_FMT_DP_REG, 0, {0}}, + {MN("ands.w"), M32_DPREG(0u, 1u), MK_DPREG, ARM_FMT_DP_REG, 0, {0}}, + {MN("bic.w"), M32_DPREG(1u, 0u), MK_DPREG, ARM_FMT_DP_REG, 0, {0}}, + {MN("orr.w"), M32_DPREG(2u, 0u), MK_DPREG, ARM_FMT_DP_REG, 0, {0}}, + {MN("orn.w"), M32_DPREG(3u, 0u), MK_DPREG, ARM_FMT_DP_REG, 0, {0}}, + {MN("eor.w"), M32_DPREG(4u, 0u), MK_DPREG, ARM_FMT_DP_REG, 0, {0}}, + {MN("add.w"), M32_DPREG(8u, 0u), MK_DPREG, ARM_FMT_DP_REG, 0, {0}}, + {MN("adds.w"), M32_DPREG(8u, 1u), MK_DPREG, ARM_FMT_DP_REG, 0, {0}}, + {MN("adc.w"), M32_DPREG(10u, 0u), MK_DPREG, ARM_FMT_DP_REG, 0, {0}}, + {MN("sbc.w"), M32_DPREG(11u, 0u), MK_DPREG, ARM_FMT_DP_REG, 0, {0}}, + {MN("sub.w"), M32_DPREG(13u, 0u), MK_DPREG, ARM_FMT_DP_REG, 0, {0}}, + {MN("subs.w"), M32_DPREG(13u, 1u), MK_DPREG, ARM_FMT_DP_REG, 0, {0}}, + {MN("rsb.w"), M32_DPREG(14u, 0u), MK_DPREG, ARM_FMT_DP_REG, 0, {0}}, + + /* ================================================================= + * Shifts (immediate): MOV.W rd, rm, <type> #sh — hw1 = 0xEA4F, type in + * hw2[5:4], imm3:imm2 the shift amount. These overlap the MOV_REG row + * above (which is shift type=LSL imm=0); the shift-imm rows are tighter + * (pin type != 0 or imm != 0) so they sit BEFORE and win. We model all + * four as a single shift-imm family keyed on hw2[5:4] in the printer. + * ================================================================= */ + {MN("lsl.w"), (0xea4fu << 16) | 0x0000u, 0xffef8030u, ARM_FMT_SHIFT_IMM, 0, + {0}}, + {MN("lsr.w"), (0xea4fu << 16) | 0x0010u, 0xffef8030u, ARM_FMT_SHIFT_IMM, 0, + {0}}, + {MN("asr.w"), (0xea4fu << 16) | 0x0020u, 0xffef8030u, ARM_FMT_SHIFT_IMM, 0, + {0}}, + {MN("ror.w"), (0xea4fu << 16) | 0x0030u, 0xffef8030u, ARM_FMT_SHIFT_IMM, 0, + {0}}, + + /* Shifts (register): LSL/LSR/ASR/ROR rd, rn, rm — hw1 = 0xFA0x, type in + * hw1[6:5] (LSL=0xFA00, LSR=0xFA20, ASR=0xFA40, ROR=0xFA60), hw2=0xF0x0. */ + {MN("lsl.w"), (0xfa00u << 16) | 0xf000u, 0xffe0f0f0u, ARM_FMT_SHIFT_REG, 0, + {0}}, + {MN("lsr.w"), (0xfa20u << 16) | 0xf000u, 0xffe0f0f0u, ARM_FMT_SHIFT_REG, 0, + {0}}, + {MN("asr.w"), (0xfa40u << 16) | 0xf000u, 0xffe0f0f0u, ARM_FMT_SHIFT_REG, 0, + {0}}, + {MN("ror.w"), (0xfa60u << 16) | 0xf000u, 0xffe0f0f0u, ARM_FMT_SHIFT_REG, 0, + {0}}, + + /* ================================================================= + * MOVW / MOVT (16-bit immediate); ADDW / SUBW (12-bit immediate). + * ================================================================= */ + {MN("movw"), (0xf2400000u), 0xfbf08000u, ARM_FMT_MOVW, 0, {0}}, + {MN("movt"), (0xf2c00000u), 0xfbf08000u, ARM_FMT_MOVW, 0, {0}}, + {MN("addw"), (0xf2000000u), 0xfbf08000u, ARM_FMT_ADDW, 0, {0}}, + {MN("subw"), (0xf2a00000u), 0xfbf08000u, ARM_FMT_ADDW, 0, {0}}, + + /* ================================================================= + * Multiply / divide. + * ================================================================= */ + /* MUL is MLA with ra=1111; pin ra=1111 + place before MLA. */ + {MN("mul"), (0xfb000000u) | (0xfu << 12), 0xfff0f0f0u, ARM_FMT_MUL, 0, {0}}, + {MN("mla"), (0xfb000000u), 0xfff000f0u, ARM_FMT_MLA, 0, {0}}, + {MN("mls"), (0xfb000010u), 0xfff000f0u, ARM_FMT_MLA, 0, {0}}, + {MN("umull"), (0xfba00000u), 0xfff000f0u, ARM_FMT_MULL, 0, {0}}, + {MN("smull"), (0xfb800000u), 0xfff000f0u, ARM_FMT_MULL, 0, {0}}, + {MN("sdiv"), (0xfb90f0f0u), 0xfff0f0f0u, ARM_FMT_DIV, 0, {0}}, + {MN("udiv"), (0xfbb0f0f0u), 0xfff0f0f0u, ARM_FMT_DIV, 0, {0}}, + + /* ================================================================= + * Sign/zero extends (hw1 = 0xFAxF, rn=1111, hw2 = 0xF080). + * ================================================================= */ + {MN("sxth"), (0xfa0ff080u), 0xfffff0c0u, ARM_FMT_EXT, 0, {0}}, + {MN("uxth"), (0xfa1ff080u), 0xfffff0c0u, ARM_FMT_EXT, 0, {0}}, + {MN("sxtb"), (0xfa4ff080u), 0xfffff0c0u, ARM_FMT_EXT, 0, {0}}, + {MN("uxtb"), (0xfa5ff080u), 0xfffff0c0u, ARM_FMT_EXT, 0, {0}}, + + /* ================================================================= + * REV / REV16 / REVSH / RBIT / CLZ. + * ================================================================= */ + {MN("rev"), (0xfa90f080u), 0xfff0f0f0u, ARM_FMT_REV, 0, {0}}, + {MN("rev16"), (0xfa90f090u), 0xfff0f0f0u, ARM_FMT_REV, 0, {0}}, + {MN("rbit"), (0xfa90f0a0u), 0xfff0f0f0u, ARM_FMT_REV, 0, {0}}, + {MN("revsh"), (0xfa90f0b0u), 0xfff0f0f0u, ARM_FMT_REV, 0, {0}}, + {MN("clz"), (0xfab0f080u), 0xfff0f0f0u, ARM_FMT_REV, 0, {0}}, + + /* ================================================================= + * Bitfield: BFC (rn=1111) before BFI; SBFX/UBFX. + * ================================================================= */ + {MN("bfc"), (0xf36f0000u), 0xffff8020u, ARM_FMT_BFC, ARM_ASMFL_ALIAS, {0}}, + {MN("bfi"), (0xf3600000u), 0xfff08020u, ARM_FMT_BFI, 0, {0}}, + {MN("sbfx"), (0xf3400000u), 0xfff08020u, ARM_FMT_BFX, 0, {0}}, + {MN("ubfx"), (0xf3c00000u), 0xfff08020u, ARM_FMT_BFX, 0, {0}}, + + /* ================================================================= + * Loads / stores — T3 (positive imm12). hw1 selects op+width: + * LDR 0xF8D0 STR 0xF8C0 LDRB 0xF890 STRB 0xF880 + * LDRH 0xF8B0 STRH 0xF8A0 LDRSB 0xF990 LDRSH 0xF9B0. + * ================================================================= */ + {MN("strb.w"), (0xf8800000u), 0xfff00000u, ARM_FMT_LDST_T3, 0, {0}}, + {MN("ldrb.w"), (0xf8900000u), 0xfff00000u, ARM_FMT_LDST_T3, 0, {0}}, + {MN("strh.w"), (0xf8a00000u), 0xfff00000u, ARM_FMT_LDST_T3, 0, {0}}, + {MN("ldrh.w"), (0xf8b00000u), 0xfff00000u, ARM_FMT_LDST_T3, 0, {0}}, + {MN("str.w"), (0xf8c00000u), 0xfff00000u, ARM_FMT_LDST_T3, 0, {0}}, + {MN("ldr.w"), (0xf8d00000u), 0xfff00000u, ARM_FMT_LDST_T3, 0, {0}}, + {MN("ldrsb.w"), (0xf9900000u), 0xfff00000u, ARM_FMT_LDST_T3, 0, {0}}, + {MN("ldrsh.w"), (0xf9b00000u), 0xfff00000u, ARM_FMT_LDST_T3, 0, {0}}, + + /* Loads / stores — T4 (±imm8, offset addressing P=1,U=add,W=0 => hw2[11:8] + * = 0b1100|U). hw1 bases: LDR 0xF850 STR 0xF840 LDRB 0xF810 STRB 0xF800 + * LDRH 0xF830 STRH 0xF820 LDRSB 0xF910 LDRSH 0xF930. Pin hw2[11]=1, + * hw2[10]=1 (P=1,W=0), hw2[8]=0; U (hw2[9]) free. */ + {MN("strb.w"), (0xf8000c00u), 0xfff00d00u, ARM_FMT_LDST_T4, 0, {0}}, + {MN("ldrb.w"), (0xf8100c00u), 0xfff00d00u, ARM_FMT_LDST_T4, 0, {0}}, + {MN("strh.w"), (0xf8200c00u), 0xfff00d00u, ARM_FMT_LDST_T4, 0, {0}}, + {MN("ldrh.w"), (0xf8300c00u), 0xfff00d00u, ARM_FMT_LDST_T4, 0, {0}}, + {MN("str.w"), (0xf8400c00u), 0xfff00d00u, ARM_FMT_LDST_T4, 0, {0}}, + {MN("ldr.w"), (0xf8500c00u), 0xfff00d00u, ARM_FMT_LDST_T4, 0, {0}}, + {MN("ldrsb.w"), (0xf9100c00u), 0xfff00d00u, ARM_FMT_LDST_T4, 0, {0}}, + {MN("ldrsh.w"), (0xf9300c00u), 0xfff00d00u, ARM_FMT_LDST_T4, 0, {0}}, + + /* ================================================================= + * Exclusive load/store; barriers; table branch. + * ================================================================= */ + {MN("ldrex"), (0xe8500f00u), 0xfff00f00u, ARM_FMT_LDREX, 0, {0}}, + {MN("strex"), (0xe8400000u), 0xfff00000u, ARM_FMT_STREX, 0, {0}}, + {MN("dsb"), (0xf3bf8f40u), 0xfffffff0u, ARM_FMT_BARRIER, 0, {0}}, + {MN("dmb"), (0xf3bf8f50u), 0xfffffff0u, ARM_FMT_BARRIER, 0, {0}}, + {MN("isb"), (0xf3bf8f60u), 0xfffffff0u, ARM_FMT_BARRIER, 0, {0}}, + {MN("tbb"), (0xe8d0f000u), 0xfff0fff0u, ARM_FMT_TB, 0, {0}}, + {MN("tbh"), (0xe8d0f010u), 0xfff0fff0u, ARM_FMT_TB, 0, {0}}, + + /* ================================================================= + * PUSH.W / POP.W (STMDB sp! / LDMIA sp!). + * ================================================================= */ + {MN("push.w"), (0xe92d0000u), 0xffff0000u, ARM_FMT_PUSHPOP, 0, {0}}, + {MN("pop.w"), (0xe8bd0000u), 0xffff0000u, ARM_FMT_PUSHPOP, 0, {0}}, + + /* ================================================================= + * 32-bit branches: BL (T1), B.W (T4), B<cond>.W (T3). + * BL: hw1=0xF0xx, hw2[15:14]=11, hw2[12]=1. + * B.W: hw1=0xF0xx, hw2[15:14]=10, hw2[12]=1. + * Bcond.W: hw1=0xF0xx (cond in hw1[9:6]), hw2[15:14]=10, hw2[12]=0. + * Order BL and B.W before Bcond.W and pin the hw2[15,12] selectors. + * ================================================================= */ + {MN("bl"), (0xf000d000u), 0xf800d000u, ARM_FMT_BL, 0, {0}}, + {MN("b.w"), (0xf0009000u), 0xf800d000u, ARM_FMT_BRANCH_T4, 0, {0}}, + {MN("b.w"), (0xf0008000u), 0xf800d000u, ARM_FMT_BRANCH_T3, 0, {0}}, + + /* ================================================================= + * 16-bit instructions (ARM_FMT_W16 — matched against the low 16 bits). + * Aliases first. + * ================================================================= */ + {MN("nop"), 0x0000bf00u, 0x0000ffffu, ARM_FMT_NONE, ARM_FMT_W16, {0}}, + {MN("yield"), 0x0000bf10u, 0x0000ffffu, ARM_FMT_NONE, ARM_FMT_W16, {0}}, + {MN("wfe"), 0x0000bf20u, 0x0000ffffu, ARM_FMT_NONE, ARM_FMT_W16, {0}}, + {MN("wfi"), 0x0000bf30u, 0x0000ffffu, ARM_FMT_NONE, ARM_FMT_W16, {0}}, + {MN("sev"), 0x0000bf40u, 0x0000ffffu, ARM_FMT_NONE, ARM_FMT_W16, {0}}, + /* IT block: 0xBFxx with mask (low nibble) != 0. NOP (0xBF00) above is the + * mask==0 hint; pin mask nonzero is implicit by ordering after the hints. */ + {MN("it"), 0x0000bf00u, 0x0000ff00u, ARM_FMT_IT, ARM_FMT_W16, {0}}, + {MN("bkpt"), 0x0000be00u, 0x0000ff00u, ARM_FMT_BKPT, ARM_FMT_W16, {0}}, + /* CBZ/CBNZ: 0xB1xx (op=0)/0xB9xx (op=1); mask the i/imm5/rn fields. */ + {MN("cbz"), 0x0000b100u, 0x0000fd00u, ARM_FMT_CBZ, ARM_FMT_W16, {0}}, + {MN("cbnz"), 0x0000b900u, 0x0000fd00u, ARM_FMT_CBZ, ARM_FMT_W16, {0}}, + /* 16-bit sign/zero-extend + byte-reverse (rd[2:0], rm[5:3]). clang prefers + * these over the 32-bit forms when rd/rm are low registers. */ + {MN("sxth"), 0x0000b200u, 0x0000ffc0u, ARM_FMT_EXT16, ARM_FMT_W16, {0}}, + {MN("sxtb"), 0x0000b240u, 0x0000ffc0u, ARM_FMT_EXT16, ARM_FMT_W16, {0}}, + {MN("uxth"), 0x0000b280u, 0x0000ffc0u, ARM_FMT_EXT16, ARM_FMT_W16, {0}}, + {MN("uxtb"), 0x0000b2c0u, 0x0000ffc0u, ARM_FMT_EXT16, ARM_FMT_W16, {0}}, + {MN("rev"), 0x0000ba00u, 0x0000ffc0u, ARM_FMT_EXT16, ARM_FMT_W16, {0}}, + {MN("rev16"), 0x0000ba40u, 0x0000ffc0u, ARM_FMT_EXT16, ARM_FMT_W16, {0}}, + {MN("revsh"), 0x0000bac0u, 0x0000ffc0u, ARM_FMT_EXT16, ARM_FMT_W16, {0}}, + /* BX/BLX (register, T1): 0x4700 (BX) / 0x4780 (BLX). */ + {MN("bx"), 0x00004700u, 0x0000ff87u, ARM_FMT_BX, ARM_FMT_W16, {0}}, + {MN("blx"), 0x00004780u, 0x0000ff87u, ARM_FMT_BX, ARM_FMT_W16, {0}}, + /* Hi-register ADD/CMP/MOV (T1/T2): 0x4400/0x4500/0x4600. MOV is its own + * format (handles sp/pc); ADD/CMP use HIREG_16. */ + {MN("add"), 0x00004400u, 0x0000ff00u, ARM_FMT_HIREG_16, ARM_FMT_W16, {0}}, + {MN("cmp"), 0x00004500u, 0x0000ff00u, ARM_FMT_HIREG_16, ARM_FMT_W16, {0}}, + /* MOV (register, high form T1): 0x4600, full r0..r15 incl. sp/pc. */ + {MN("mov"), 0x00004600u, 0x0000ff00u, ARM_FMT_MOVHI16, ARM_FMT_W16, {0}}, + + /* 16-bit data-processing register (T1): 0x4000 | (op<<6). rdn[2:0], rm[5:3]. + * MUL prints 3 operands (rdm, rn, rdm); the rest print rdn, rm. */ + {MN("ands"), 0x00004000u, 0x0000ffc0u, ARM_FMT_ALU_16, ARM_FMT_W16, {0}}, + {MN("eors"), 0x00004040u, 0x0000ffc0u, ARM_FMT_ALU_16, ARM_FMT_W16, {0}}, + {MN("lsls"), 0x00004080u, 0x0000ffc0u, ARM_FMT_ALU_16, ARM_FMT_W16, {0}}, + {MN("lsrs"), 0x000040c0u, 0x0000ffc0u, ARM_FMT_ALU_16, ARM_FMT_W16, {0}}, + {MN("asrs"), 0x00004100u, 0x0000ffc0u, ARM_FMT_ALU_16, ARM_FMT_W16, {0}}, + {MN("adcs"), 0x00004140u, 0x0000ffc0u, ARM_FMT_ALU_16, ARM_FMT_W16, {0}}, + {MN("sbcs"), 0x00004180u, 0x0000ffc0u, ARM_FMT_ALU_16, ARM_FMT_W16, {0}}, + {MN("rors"), 0x000041c0u, 0x0000ffc0u, ARM_FMT_ALU_16, ARM_FMT_W16, {0}}, + {MN("tst"), 0x00004200u, 0x0000ffc0u, ARM_FMT_ALU_16, ARM_FMT_W16, {0}}, + {MN("rsbs"), 0x00004240u, 0x0000ffc0u, ARM_FMT_ALU_16, ARM_FMT_W16, {0}}, + {MN("cmp"), 0x00004280u, 0x0000ffc0u, ARM_FMT_ALU_16, ARM_FMT_W16, {0}}, + {MN("cmn"), 0x000042c0u, 0x0000ffc0u, ARM_FMT_ALU_16, ARM_FMT_W16, {0}}, + {MN("orrs"), 0x00004300u, 0x0000ffc0u, ARM_FMT_ALU_16, ARM_FMT_W16, {0}}, + {MN("muls"), 0x00004340u, 0x0000ffc0u, ARM_FMT_ALU_16, ARM_FMT_W16, {0}}, + {MN("bics"), 0x00004380u, 0x0000ffc0u, ARM_FMT_ALU_16, ARM_FMT_W16, {0}}, + {MN("mvns"), 0x000043c0u, 0x0000ffc0u, ARM_FMT_ALU_16, ARM_FMT_W16, {0}}, + + /* 16-bit ADD/SUB register (T1) + ADD/SUB imm3 (T1). 0x1800/0x1A00 reg, + * 0x1C00/0x1E00 imm3. */ + {MN("adds"), 0x00001800u, 0x0000fe00u, ARM_FMT_ADDSUBR_16, ARM_FMT_W16, {0}}, + {MN("subs"), 0x00001a00u, 0x0000fe00u, ARM_FMT_ADDSUBR_16, ARM_FMT_W16, {0}}, + {MN("adds"), 0x00001c00u, 0x0000fe00u, ARM_FMT_ADDSUB3_16, ARM_FMT_W16, {0}}, + {MN("subs"), 0x00001e00u, 0x0000fe00u, ARM_FMT_ADDSUB3_16, ARM_FMT_W16, {0}}, + + /* 16-bit shift-immediate (T1): 0x0000(lsls)/0x0800(lsrs)/0x1000(asrs). + * These overlap movs (0x0000 lsls #0 == movs); placed after to let the + * imm5!=0 forms decode. lsls #0 with imm5=0 is actually `movs` but we keep + * lsls for fidelity. */ + {MN("lsls"), 0x00000000u, 0x0000f800u, ARM_FMT_SHIFTI_16, ARM_FMT_W16, {0}}, + {MN("lsrs"), 0x00000800u, 0x0000f800u, ARM_FMT_SHIFTI_16, ARM_FMT_W16, {0}}, + {MN("asrs"), 0x00001000u, 0x0000f800u, ARM_FMT_SHIFTI_16, ARM_FMT_W16, {0}}, + + /* 16-bit MOV/CMP/ADD/SUB immediate-8 (T1/T2): 0x2000/0x2800/0x3000/0x3800. + * rd/rn in [10:8], imm8 in [7:0]. */ + {MN("movs"), 0x00002000u, 0x0000f800u, ARM_FMT_DPI8_16, ARM_FMT_W16, {0}}, + {MN("cmp"), 0x00002800u, 0x0000f800u, ARM_FMT_DPI8_16, ARM_FMT_W16, {0}}, + {MN("adds"), 0x00003000u, 0x0000f800u, ARM_FMT_DPI8_16, ARM_FMT_W16, {0}}, + {MN("subs"), 0x00003800u, 0x0000f800u, ARM_FMT_DPI8_16, ARM_FMT_W16, {0}}, + + /* 16-bit LDR/STR{,B,H} immediate (T1): rt[2:0], rn[5:3], imm5[10:6] scaled + * by 4/1/2 by width. 0x6000 str / 0x6800 ldr / 0x7000 strb / 0x7800 ldrb / + * 0x8000 strh / 0x8800 ldrh. */ + {MN("str"), 0x00006000u, 0x0000f800u, ARM_FMT_LDSTI5_16, ARM_FMT_W16, {0}}, + {MN("ldr"), 0x00006800u, 0x0000f800u, ARM_FMT_LDSTI5_16, ARM_FMT_W16, {0}}, + {MN("strb"), 0x00007000u, 0x0000f800u, ARM_FMT_LDSTI5_16, ARM_FMT_W16, {0}}, + {MN("ldrb"), 0x00007800u, 0x0000f800u, ARM_FMT_LDSTI5_16, ARM_FMT_W16, {0}}, + {MN("strh"), 0x00008000u, 0x0000f800u, ARM_FMT_LDSTI5_16, ARM_FMT_W16, {0}}, + {MN("ldrh"), 0x00008800u, 0x0000f800u, ARM_FMT_LDSTI5_16, ARM_FMT_W16, {0}}, + + /* 16-bit LDR/STR sp-relative (T2): rt[10:8], imm8*4. 0x9000 str / 0x9800 + * ldr. */ + {MN("str"), 0x00009000u, 0x0000f800u, ARM_FMT_LDSTSP_16, ARM_FMT_W16, {0}}, + {MN("ldr"), 0x00009800u, 0x0000f800u, ARM_FMT_LDSTSP_16, ARM_FMT_W16, {0}}, + + /* 16-bit ADD rd, pc/sp, #imm8*4 (T1): 0xA000 (pc) / 0xA800 (sp). */ + {MN("adr"), 0x0000a000u, 0x0000f800u, ARM_FMT_ADDSP_16, ARM_FMT_W16, {0}}, + {MN("add"), 0x0000a800u, 0x0000f800u, ARM_FMT_ADDSP_16, ARM_FMT_W16, {0}}, + + /* 16-bit ADD/SUB sp, sp, #imm7*4 (T2): 0xB000 add / 0xB080 sub. */ + {MN("add"), 0x0000b000u, 0x0000ff80u, ARM_FMT_ADJSP_16, ARM_FMT_W16, {0}}, + {MN("sub"), 0x0000b080u, 0x0000ff80u, ARM_FMT_ADJSP_16, ARM_FMT_W16, {0}}, + + /* 16-bit PUSH/POP (T1): 0xB400 push (M=bit8 -> LR) / 0xBC00 pop (P=bit8 -> + * PC). */ + {MN("push"), 0x0000b400u, 0x0000fe00u, ARM_FMT_PUSHPOP_16, ARM_FMT_W16, {0}}, + {MN("pop"), 0x0000bc00u, 0x0000fe00u, ARM_FMT_PUSHPOP_16, ARM_FMT_W16, {0}}, + /* B<cond> (T1, 8-bit): 0xDxyy (cond in [11:8], not 1110/1111). The 0xDE/DF + * slots are UDF/SVC; the cond test in the printer guards them. */ + {MN("b"), 0x0000d000u, 0x0000f000u, ARM_FMT_BCC16, ARM_FMT_W16, {0}}, + /* B (T2, 11-bit unconditional): 0xExxx (top 5 bits 11100). */ + {MN("b"), 0x0000e000u, 0x0000f800u, ARM_FMT_B16, ARM_FMT_W16, {0}}, +}; + +const u32 arm32_insn_table_n = + (u32)(sizeof arm32_insn_table / sizeof arm32_insn_table[0]); + +#undef MN + +const Arm32InsnDesc* arm32_disasm_find(u32 word, int is16) { + u32 w = is16 ? (word & 0xffffu) : word; + for (u32 i = 0; i < arm32_insn_table_n; ++i) { + const Arm32InsnDesc* d = &arm32_insn_table[i]; + int row16 = (d->flags & ARM_FMT_W16) != 0; + if (row16 != (is16 != 0)) continue; + if ((w & d->mask) == d->match) return d; + } + return NULL; +} + +const Arm32InsnDesc* arm32_asm_find(Slice mnemonic) { + if (!mnemonic.s) return NULL; + /* Prefer the canonical (non-alias) row; fall back to aliases. */ + for (u32 i = 0; i < arm32_insn_table_n; ++i) { + const Arm32InsnDesc* d = &arm32_insn_table[i]; + if (d->flags & ARM_ASMFL_ALIAS) continue; + if (slice_eq(d->mnemonic, mnemonic)) return d; + } + for (u32 i = 0; i < arm32_insn_table_n; ++i) { + const Arm32InsnDesc* d = &arm32_insn_table[i]; + if (slice_eq(d->mnemonic, mnemonic)) return d; + } + return NULL; +} + +/* ===================================================================== + * Condition codes. + * ===================================================================== */ +static const char* const ARM_CC_NAMES[16] = { + "eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc", + "hi", "ls", "ge", "lt", "gt", "le", "", "", +}; + +const char* arm32_cond_name(u32 cond) { + return ARM_CC_NAMES[cond & 0xfu]; +} + +int arm32_cond_from_name(Slice s) { + for (u32 i = 0; i < 14u; ++i) { + if (slice_eq_cstr(s, ARM_CC_NAMES[i])) return (int)i; + } + if (slice_eq_cstr(s, "al")) return (int)ARM_CC_AL; + if (slice_eq_cstr(s, "hs")) return (int)ARM_CC_CS; + if (slice_eq_cstr(s, "lo")) return (int)ARM_CC_CC; + return -1; +} + +/* ===================================================================== + * Operand printing. + * ===================================================================== */ + +static const char* const ARM_RNAMES[16] = { + "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", + "r8", "r9", "r10", "r11", "ip", "sp", "lr", "pc", +}; + +static void p_reg(StrBuf* sb, u32 r) { strbuf_puts(sb, ARM_RNAMES[r & 0xfu]); } +static void p_sep(StrBuf* sb) { strbuf_puts(sb, ", "); } +static void p_imm(StrBuf* sb, i64 v) { + strbuf_putc(sb, '#'); + strbuf_put_i64(sb, v); +} +static void p_rel(StrBuf* sb, u64 vaddr, i64 off) { + if (vaddr) + strbuf_put_hex_u64(sb, vaddr + (u64)off); + else { + strbuf_putc(sb, '#'); + strbuf_put_i64(sb, off); + } +} + +/* ThumbExpandImm: decode the 12-bit i:imm3:imm8 modified immediate to its + * 32-bit value. Inverse of thumb_expand_imm_encode. */ +static u32 thumb_expand_imm_decode(u32 imm12) { + u32 imm8 = imm12 & 0xffu; + u32 ctl = (imm12 >> 8) & 0xfu; /* i:imm3 */ + if ((ctl & 0xcu) == 0u) { + switch (ctl & 3u) { + case 0: + return imm8; + case 1: + return (imm8 << 16) | imm8; + case 2: + return (imm8 << 24) | (imm8 << 8); + default: + return (imm8 << 24) | (imm8 << 16) | (imm8 << 8) | imm8; + } + } + { + /* Rotated form: imm12[11:7] = rotation (8..31), imm12[6:0] = base[6:0], + * base[7] implicitly 1. value = ror(base, rot). */ + u32 base = 0x80u | (imm8 & 0x7fu); + u32 rot = (imm12 >> 7) & 0x1fu; + return (base >> rot) | (base << (32u - rot)); + } +} + +/* ---- 32-bit field extractors ---- */ +static u32 f_hw1(u32 w) { return (w >> 16) & 0xffffu; } +static u32 f_hw2(u32 w) { return w & 0xffffu; } +static u32 f_rn(u32 w) { return f_hw1(w) & 0xfu; } +static u32 f_rd(u32 w) { return (f_hw2(w) >> 8) & 0xfu; } +static u32 f_rm(u32 w) { return f_hw2(w) & 0xfu; } +/* i:imm3:imm8 modified immediate from a 0xF0xx DP-imm / MOVW form. */ +static u32 f_modimm12(u32 w) { + u32 i = (f_hw1(w) >> 10) & 1u; + u32 imm3 = (f_hw2(w) >> 12) & 7u; + u32 imm8 = f_hw2(w) & 0xffu; + return (i << 11) | (imm3 << 8) | imm8; +} + +static void print_dp_reg(StrBuf* sb, u32 w) { + p_reg(sb, f_rd(w)); + p_sep(sb); + p_reg(sb, f_rn(w)); + p_sep(sb); + p_reg(sb, f_rm(w)); + /* Optional shift on rm (imm3:imm2 in hw2[14:12]/[7:6], type hw2[5:4]). */ + { + u32 imm3 = (f_hw2(w) >> 12) & 7u, imm2 = (f_hw2(w) >> 6) & 3u; + u32 type = (f_hw2(w) >> 4) & 3u; + u32 sh = (imm3 << 2) | imm2; + static const char* const SH[4] = {"lsl", "lsr", "asr", "ror"}; + if (sh != 0u || type != 0u) { + p_sep(sb); + strbuf_puts(sb, SH[type]); + strbuf_putc(sb, ' '); + p_imm(sb, (i64)(sh == 0u && type != 0u ? 32u : sh)); + } + } +} + +static void print_dp_imm(StrBuf* sb, u32 w) { + p_reg(sb, f_rd(w)); + p_sep(sb); + p_reg(sb, f_rn(w)); + p_sep(sb); + p_imm(sb, (i64)(i32)thumb_expand_imm_decode(f_modimm12(w))); +} + +static void print_mov_imm(StrBuf* sb, u32 w) { + p_reg(sb, f_rd(w)); + p_sep(sb); + p_imm(sb, (i64)(i32)thumb_expand_imm_decode(f_modimm12(w))); +} + +static void print_mov_reg(StrBuf* sb, u32 w) { + p_reg(sb, f_rd(w)); + p_sep(sb); + p_reg(sb, f_rm(w)); +} + +static void print_cmp_reg(StrBuf* sb, u32 w) { + p_reg(sb, f_rn(w)); + p_sep(sb); + p_reg(sb, f_rm(w)); +} + +static void print_cmp_imm(StrBuf* sb, u32 w) { + p_reg(sb, f_rn(w)); + p_sep(sb); + p_imm(sb, (i64)(i32)thumb_expand_imm_decode(f_modimm12(w))); +} + +static void print_movw(StrBuf* sb, u32 w) { + /* imm16 = imm4:i:imm3:imm8. imm4 = hw1[3:0]. */ + u32 imm4 = f_hw1(w) & 0xfu; + u32 i = (f_hw1(w) >> 10) & 1u; + u32 imm3 = (f_hw2(w) >> 12) & 7u; + u32 imm8 = f_hw2(w) & 0xffu; + u32 imm16 = (imm4 << 12) | (i << 11) | (imm3 << 8) | imm8; + p_reg(sb, f_rd(w)); + p_sep(sb); + strbuf_putc(sb, '#'); + strbuf_put_u64(sb, (u64)imm16); +} + +static void print_addw(StrBuf* sb, u32 w) { + u32 i = (f_hw1(w) >> 10) & 1u; + u32 imm3 = (f_hw2(w) >> 12) & 7u; + u32 imm8 = f_hw2(w) & 0xffu; + u32 imm12 = (i << 11) | (imm3 << 8) | imm8; + p_reg(sb, f_rd(w)); + p_sep(sb); + p_reg(sb, f_rn(w)); + p_sep(sb); + p_imm(sb, (i64)imm12); +} + +static void print_shift_imm(StrBuf* sb, u32 w) { + u32 imm3 = (f_hw2(w) >> 12) & 7u, imm2 = (f_hw2(w) >> 6) & 3u; + u32 sh = (imm3 << 2) | imm2; + u32 type = (f_hw2(w) >> 4) & 3u; + p_reg(sb, f_rd(w)); + p_sep(sb); + p_reg(sb, f_rm(w)); + p_sep(sb); + /* LSR/ASR shift of 0 means 32. */ + p_imm(sb, (i64)((sh == 0u && (type == 1u || type == 2u)) ? 32u : sh)); +} + +static void print_shift_reg(StrBuf* sb, u32 w) { + p_reg(sb, f_rd(w)); + p_sep(sb); + p_reg(sb, f_rn(w)); + p_sep(sb); + p_reg(sb, f_rm(w)); +} + +static void print_mul(StrBuf* sb, u32 w) { + p_reg(sb, f_rd(w)); + p_sep(sb); + p_reg(sb, f_rn(w)); + p_sep(sb); + p_reg(sb, f_rm(w)); +} + +static void print_mla(StrBuf* sb, u32 w) { + u32 ra = (f_hw2(w) >> 12) & 0xfu; + p_reg(sb, f_rd(w)); + p_sep(sb); + p_reg(sb, f_rn(w)); + p_sep(sb); + p_reg(sb, f_rm(w)); + p_sep(sb); + p_reg(sb, ra); +} + +static void print_div(StrBuf* sb, u32 w) { + p_reg(sb, f_rd(w)); + p_sep(sb); + p_reg(sb, f_rn(w)); + p_sep(sb); + p_reg(sb, f_rm(w)); +} + +static void print_mull(StrBuf* sb, u32 w) { + u32 rdlo = (f_hw2(w) >> 12) & 0xfu; + u32 rdhi = (f_hw2(w) >> 8) & 0xfu; + p_reg(sb, rdlo); + p_sep(sb); + p_reg(sb, rdhi); + p_sep(sb); + p_reg(sb, f_rn(w)); + p_sep(sb); + p_reg(sb, f_rm(w)); +} + +static void print_ext(StrBuf* sb, u32 w) { + p_reg(sb, f_rd(w)); + p_sep(sb); + p_reg(sb, f_rm(w)); +} + +static void print_rev(StrBuf* sb, u32 w) { + p_reg(sb, f_rd(w)); + p_sep(sb); + p_reg(sb, f_rm(w)); +} + +static void print_bfx(StrBuf* sb, u32 w) { + u32 imm3 = (f_hw2(w) >> 12) & 7u, imm2 = (f_hw2(w) >> 6) & 3u; + u32 lsb = (imm3 << 2) | imm2; + u32 wm1 = f_hw2(w) & 0x1fu; + p_reg(sb, f_rd(w)); + p_sep(sb); + p_reg(sb, f_rn(w)); + p_sep(sb); + p_imm(sb, (i64)lsb); + p_sep(sb); + p_imm(sb, (i64)(wm1 + 1u)); +} + +static void print_bfi(StrBuf* sb, u32 w) { + u32 imm3 = (f_hw2(w) >> 12) & 7u, imm2 = (f_hw2(w) >> 6) & 3u; + u32 lsb = (imm3 << 2) | imm2; + u32 msb = f_hw2(w) & 0x1fu; + p_reg(sb, f_rd(w)); + p_sep(sb); + p_reg(sb, f_rn(w)); + p_sep(sb); + p_imm(sb, (i64)lsb); + p_sep(sb); + p_imm(sb, (i64)(msb - lsb + 1u)); +} + +static void print_bfc(StrBuf* sb, u32 w) { + u32 imm3 = (f_hw2(w) >> 12) & 7u, imm2 = (f_hw2(w) >> 6) & 3u; + u32 lsb = (imm3 << 2) | imm2; + u32 msb = f_hw2(w) & 0x1fu; + p_reg(sb, f_rd(w)); + p_sep(sb); + p_imm(sb, (i64)lsb); + p_sep(sb); + p_imm(sb, (i64)(msb - lsb + 1u)); +} + +static void print_ldst_t3(StrBuf* sb, u32 w) { + u32 rt = (f_hw2(w) >> 12) & 0xfu; + u32 imm12 = f_hw2(w) & 0xfffu; + p_reg(sb, rt); + p_sep(sb); + strbuf_putc(sb, '['); + p_reg(sb, f_rn(w)); + if (imm12) { + p_sep(sb); + p_imm(sb, (i64)imm12); + } + strbuf_putc(sb, ']'); +} + +static void print_ldst_t4(StrBuf* sb, u32 w) { + u32 rt = (f_hw2(w) >> 12) & 0xfu; + u32 add = (f_hw2(w) >> 9) & 1u; + u32 imm8 = f_hw2(w) & 0xffu; + p_reg(sb, rt); + p_sep(sb); + strbuf_putc(sb, '['); + p_reg(sb, f_rn(w)); + if (imm8) { + p_sep(sb); + p_imm(sb, add ? (i64)imm8 : -(i64)imm8); + } + strbuf_putc(sb, ']'); +} + +static void print_ldrex(StrBuf* sb, u32 w) { + u32 rt = (f_hw2(w) >> 12) & 0xfu; + u32 imm8 = f_hw2(w) & 0xffu; + p_reg(sb, rt); + p_sep(sb); + strbuf_putc(sb, '['); + p_reg(sb, f_rn(w)); + if (imm8) { + p_sep(sb); + p_imm(sb, (i64)(imm8 * 4u)); + } + strbuf_putc(sb, ']'); +} + +static void print_strex(StrBuf* sb, u32 w) { + u32 rd = (f_hw2(w) >> 8) & 0xfu; + u32 rt = (f_hw2(w) >> 12) & 0xfu; + u32 imm8 = f_hw2(w) & 0xffu; + p_reg(sb, rd); + p_sep(sb); + p_reg(sb, rt); + p_sep(sb); + strbuf_putc(sb, '['); + p_reg(sb, f_rn(w)); + if (imm8) { + p_sep(sb); + p_imm(sb, (i64)(imm8 * 4u)); + } + strbuf_putc(sb, ']'); +} + +static void print_reglist(StrBuf* sb, u32 reglist) { + int first = 1; + strbuf_putc(sb, '{'); + for (u32 r = 0; r < 16u; ++r) { + if (reglist & (1u << r)) { + if (!first) p_sep(sb); + p_reg(sb, r); + first = 0; + } + } + strbuf_putc(sb, '}'); +} + +static void print_pushpop(StrBuf* sb, u32 w) { + print_reglist(sb, f_hw2(w)); +} + +static void print_barrier(StrBuf* sb, u32 w) { + u32 opt = f_hw2(w) & 0xfu; + if (opt == 0xfu) + strbuf_puts(sb, "sy"); + else + strbuf_put_u64(sb, (u64)opt); +} + +static void print_tb(StrBuf* sb, u32 w) { + strbuf_putc(sb, '['); + p_reg(sb, f_rn(w)); + p_sep(sb); + p_reg(sb, f_rm(w)); + if (((f_hw2(w) >> 4) & 1u)) strbuf_puts(sb, ", lsl #1"); + strbuf_putc(sb, ']'); +} + +/* B.W (T4) 24-bit signed offset: S:I1:I2:imm10:imm11 (imm32 in 2-byte units), + * I1=NOT(J1^S), I2=NOT(J2^S). */ +static i64 decode_branch_t4(u32 w) { + u32 s = (f_hw1(w) >> 10) & 1u; + u32 imm10 = f_hw1(w) & 0x3ffu; + u32 j1 = (f_hw2(w) >> 13) & 1u; + u32 j2 = (f_hw2(w) >> 11) & 1u; + u32 imm11 = f_hw2(w) & 0x7ffu; + u32 i1 = (~(j1 ^ s)) & 1u; + u32 i2 = (~(j2 ^ s)) & 1u; + u32 raw = (s << 24) | (i1 << 23) | (i2 << 22) | (imm10 << 12) | (imm11 << 1); + i64 off = (i64)raw; + if (s) off |= ~(i64)0x1ffffff; /* sign-extend bit 24 (after the <<1) */ + return off + 4; /* Thumb PC bias */ +} + +/* B<cond>.W (T3) 20-bit signed offset: S:J2:J1:imm6:imm11 (no XOR). */ +static i64 decode_branch_t3(u32 w) { + u32 s = (f_hw1(w) >> 10) & 1u; + u32 imm6 = f_hw1(w) & 0x3fu; + u32 j1 = (f_hw2(w) >> 13) & 1u; + u32 j2 = (f_hw2(w) >> 11) & 1u; + u32 imm11 = f_hw2(w) & 0x7ffu; + u32 raw = (s << 20) | (j2 << 19) | (j1 << 18) | (imm6 << 12) | (imm11 << 1); + i64 off = (i64)raw; + if (s) off |= ~(i64)0x1fffff; /* sign-extend bit 20 */ + return off + 4; +} + +static u32 branch_t3_cond(u32 w) { return (f_hw1(w) >> 6) & 0xfu; } + +static void print_branch_t4(StrBuf* sb, u32 w, u64 vaddr) { + p_rel(sb, vaddr, decode_branch_t4(w)); +} + +static void print_bl(StrBuf* sb, u32 w, u64 vaddr) { + p_rel(sb, vaddr, decode_branch_t4(w)); +} + +/* ---- 16-bit field extractors ---- */ +static void print_cbz(StrBuf* sb, u32 w, u64 vaddr) { + u32 hw = w & 0xffffu; + u32 rn = hw & 7u; + u32 i = (hw >> 9) & 1u, imm5 = (hw >> 3) & 0x1fu; + i64 off = (i64)((i << 6) | (imm5 << 1)) + 4; /* PC bias */ + p_reg(sb, rn); + p_sep(sb); + p_rel(sb, vaddr, off); +} + +static void print_movhi16(StrBuf* sb, u32 w) { + u32 hw = w & 0xffffu; + u32 rd = ((hw >> 7) & 1u) << 3 | (hw & 7u); + u32 rm = (hw >> 3) & 0xfu; + p_reg(sb, rd); + p_sep(sb); + p_reg(sb, rm); +} + +static void print_bx(StrBuf* sb, u32 w) { + u32 rm = (w >> 3) & 0xfu; + p_reg(sb, rm); +} + +static void print_bkpt(StrBuf* sb, u32 w) { + p_imm(sb, (i64)(w & 0xffu)); +} + +static void print_ext16(StrBuf* sb, u32 w) { + u32 rd = w & 7u, rm = (w >> 3) & 7u; + p_reg(sb, rd); + p_sep(sb); + p_reg(sb, rm); +} + +/* ---- additional 16-bit families ---- */ +static void print_dpi8_16(StrBuf* sb, u32 w) { + u32 rd = (w >> 8) & 7u, imm8 = w & 0xffu; + p_reg(sb, rd); + p_sep(sb); + p_imm(sb, (i64)imm8); +} + +static void print_addsub3_16(StrBuf* sb, u32 w) { + u32 rd = w & 7u, rn = (w >> 3) & 7u, imm3 = (w >> 6) & 7u; + p_reg(sb, rd); + p_sep(sb); + p_reg(sb, rn); + p_sep(sb); + p_imm(sb, (i64)imm3); +} + +static void print_addsubr_16(StrBuf* sb, u32 w) { + u32 rd = w & 7u, rn = (w >> 3) & 7u, rm = (w >> 6) & 7u; + p_reg(sb, rd); + p_sep(sb); + p_reg(sb, rn); + p_sep(sb); + p_reg(sb, rm); +} + +static void print_shifti_16(StrBuf* sb, u32 w) { + u32 rd = w & 7u, rm = (w >> 3) & 7u, imm5 = (w >> 6) & 0x1fu; + p_reg(sb, rd); + p_sep(sb); + p_reg(sb, rm); + p_sep(sb); + p_imm(sb, (i64)imm5); +} + +static void print_alu_16(StrBuf* sb, u32 w, const Arm32InsnDesc* d) { + u32 rdn = w & 7u, rm = (w >> 3) & 7u; + /* MULS prints `rdm, rn, rdm`; the others print `rdn, rm`. */ + if (slice_eq_cstr(d->mnemonic, "muls")) { + p_reg(sb, rdn); + p_sep(sb); + p_reg(sb, rm); + p_sep(sb); + p_reg(sb, rdn); + return; + } + p_reg(sb, rdn); + p_sep(sb); + p_reg(sb, rm); +} + +static void print_hireg_16(StrBuf* sb, u32 w) { + u32 rdn = (w & 7u) | (((w >> 7) & 1u) << 3); + u32 rm = (w >> 3) & 0xfu; + p_reg(sb, rdn); + p_sep(sb); + p_reg(sb, rm); +} + +static void print_ldsti5_16(StrBuf* sb, u32 w) { + u32 rt = w & 7u, rn = (w >> 3) & 7u, imm5 = (w >> 6) & 0x1fu; + u32 op = (w >> 11) & 0x1fu; /* 0x0C/0x0D str/ldr(*4); 0x0E/0x0F strb/ldrb(*1); + 0x10/0x11 strh/ldrh(*2) */ + u32 scale = (op <= 0x0du) ? 4u : (op <= 0x0fu) ? 1u : 2u; + p_reg(sb, rt); + p_sep(sb); + strbuf_putc(sb, '['); + p_reg(sb, rn); + if (imm5) { + p_sep(sb); + p_imm(sb, (i64)(imm5 * scale)); + } + strbuf_putc(sb, ']'); +} + +static void print_ldstsp_16(StrBuf* sb, u32 w) { + u32 rt = (w >> 8) & 7u, imm8 = w & 0xffu; + p_reg(sb, rt); + p_sep(sb); + strbuf_putc(sb, '['); + p_reg(sb, ARM_SP); + if (imm8) { + p_sep(sb); + p_imm(sb, (i64)(imm8 * 4u)); + } + strbuf_putc(sb, ']'); +} + +static void print_addsp_16(StrBuf* sb, u32 w) { + u32 rd = (w >> 8) & 7u, imm8 = w & 0xffu; + u32 sp = (w >> 11) & 1u; /* 0 = pc (adr), 1 = sp */ + p_reg(sb, rd); + p_sep(sb); + p_reg(sb, sp ? ARM_SP : ARM_PC); + p_sep(sb); + p_imm(sb, (i64)(imm8 * 4u)); +} + +static void print_adjsp_16(StrBuf* sb, u32 w) { + u32 imm7 = w & 0x7fu; + p_reg(sb, ARM_SP); + p_sep(sb); + p_reg(sb, ARM_SP); + p_sep(sb); + p_imm(sb, (i64)(imm7 * 4u)); +} + +static void print_pushpop_16(StrBuf* sb, u32 w) { + u32 list = w & 0xffu; + u32 extra = (w >> 8) & 1u; /* push: bit8 -> LR; pop: bit8 -> PC */ + if ((w >> 11) & 1u) + list |= extra ? (1u << 15) : 0u; /* pop -> PC */ + else + list |= extra ? (1u << 14) : 0u; /* push -> LR */ + print_reglist(sb, list); +} + +static void print_b16(StrBuf* sb, u32 w, u64 vaddr) { + u32 hw = w & 0xffffu; + u32 imm11 = hw & 0x7ffu; + i64 off = (i64)(imm11 << 1); + if (imm11 & 0x400u) off |= ~(i64)0xfff; /* sign-extend 12-bit */ + off += 4; + p_rel(sb, vaddr, off); +} + +static void print_bcc16(StrBuf* sb, u32 w, u64 vaddr) { + u32 hw = w & 0xffffu; + u32 imm8 = hw & 0xffu; + i64 off = (i64)(imm8 << 1); + if (imm8 & 0x80u) off |= ~(i64)0x1ff; /* sign-extend 9-bit */ + off += 4; + p_rel(sb, vaddr, off); +} + +void arm32_print_operands(StrBuf* sb, const Arm32InsnDesc* desc, u32 word, + u64 vaddr) { + switch ((Arm32Format)desc->fmt) { + case ARM_FMT_NONE: + break; + case ARM_FMT_DP_REG: + print_dp_reg(sb, word); + break; + case ARM_FMT_DP_IMM: + print_dp_imm(sb, word); + break; + case ARM_FMT_MOV_IMM: + print_mov_imm(sb, word); + break; + case ARM_FMT_CMP_REG: + print_cmp_reg(sb, word); + break; + case ARM_FMT_CMP_IMM: + print_cmp_imm(sb, word); + break; + case ARM_FMT_MOV_REG: + print_mov_reg(sb, word); + break; + case ARM_FMT_MOVW: + print_movw(sb, word); + break; + case ARM_FMT_ADDW: + print_addw(sb, word); + break; + case ARM_FMT_SHIFT_IMM: + print_shift_imm(sb, word); + break; + case ARM_FMT_SHIFT_REG: + print_shift_reg(sb, word); + break; + case ARM_FMT_MUL: + print_mul(sb, word); + break; + case ARM_FMT_MLA: + print_mla(sb, word); + break; + case ARM_FMT_DIV: + print_div(sb, word); + break; + case ARM_FMT_MULL: + print_mull(sb, word); + break; + case ARM_FMT_EXT: + print_ext(sb, word); + break; + case ARM_FMT_REV: + print_rev(sb, word); + break; + case ARM_FMT_BFX: + print_bfx(sb, word); + break; + case ARM_FMT_BFI: + print_bfi(sb, word); + break; + case ARM_FMT_BFC: + print_bfc(sb, word); + break; + case ARM_FMT_LDST_T3: + print_ldst_t3(sb, word); + break; + case ARM_FMT_LDST_T4: + print_ldst_t4(sb, word); + break; + case ARM_FMT_LDREX: + print_ldrex(sb, word); + break; + case ARM_FMT_STREX: + print_strex(sb, word); + break; + case ARM_FMT_PUSHPOP: + print_pushpop(sb, word); + break; + case ARM_FMT_BARRIER: + print_barrier(sb, word); + break; + case ARM_FMT_TB: + print_tb(sb, word); + break; + case ARM_FMT_BRANCH_T4: + print_branch_t4(sb, word, vaddr); + break; + case ARM_FMT_BRANCH_T3: + p_rel(sb, vaddr, decode_branch_t3(word)); + break; + case ARM_FMT_BL: + print_bl(sb, word, vaddr); + break; + case ARM_FMT_B16: + print_b16(sb, word, vaddr); + break; + case ARM_FMT_BCC16: + print_bcc16(sb, word, vaddr); + break; + case ARM_FMT_CBZ: + print_cbz(sb, word, vaddr); + break; + case ARM_FMT_IT: + break; /* mnemonic carries the cc suffix; no operands here */ + case ARM_FMT_MOVHI16: + print_movhi16(sb, word); + break; + case ARM_FMT_BX: + print_bx(sb, word); + break; + case ARM_FMT_BKPT: + print_bkpt(sb, word); + break; + case ARM_FMT_EXT16: + print_ext16(sb, word); + break; + case ARM_FMT_DPI8_16: + print_dpi8_16(sb, word); + break; + case ARM_FMT_ADDSUB3_16: + print_addsub3_16(sb, word); + break; + case ARM_FMT_ADDSUBR_16: + print_addsubr_16(sb, word); + break; + case ARM_FMT_SHIFTI_16: + print_shifti_16(sb, word); + break; + case ARM_FMT_ALU_16: + print_alu_16(sb, word, desc); + break; + case ARM_FMT_HIREG_16: + print_hireg_16(sb, word); + break; + case ARM_FMT_LDSTI5_16: + print_ldsti5_16(sb, word); + break; + case ARM_FMT_LDSTSP_16: + print_ldstsp_16(sb, word); + break; + case ARM_FMT_ADDSP_16: + print_addsp_16(sb, word); + break; + case ARM_FMT_ADJSP_16: + print_adjsp_16(sb, word); + break; + case ARM_FMT_PUSHPOP_16: + print_pushpop_16(sb, word); + break; + } +} + +/* B<cond>.W condition field — disasm.c appends the cc to the mnemonic. */ +u32 arm32_branch_t3_cond(u32 w) { return branch_t3_cond(w); } diff --git a/src/arch/arm32/isa.h b/src/arch/arm32/isa.h @@ -14,6 +14,8 @@ #define KIT_ARCH_ARM32_ISA_H #include "core/core.h" +#include "core/slice.h" +#include "core/strbuf.h" /* Core register numbers (AAPCS roles). */ enum { @@ -22,6 +24,15 @@ enum { ARM_R2 = 2, ARM_R3 = 3, ARM_R4 = 4, + ARM_R5 = 5, + ARM_R6 = 6, + ARM_R7 = 7, + ARM_R8 = 8, + ARM_R9 = 9, + ARM_R10 = 10, + ARM_R11 = 11, + /* The Thumb frame pointer is r7 in kit's ABI; native.c owns `#define ARM_FP + * 7u`. No ARM_FP enumerator here — it would shadow that macro and mislead. */ ARM_R12 = 12, ARM_IP = 12, /* intra-procedure scratch */ ARM_SP = 13, @@ -316,6 +327,17 @@ static inline u16 arm_revsh(u32 rd, u32 rm) { return (u16)(0xbac0u | ((rm & 7u) /* --------- barriers (32-bit T1, hw2 = 0x8f00 | op<<4 | option) --------- */ /* option = 0xf for the "sy" full-system barrier (the only form kit emits). */ +/* --------- multiply long + MLA (T1) --------- */ +static inline u32 arm_mla(u32 rd, u32 rn, u32 rm, u32 ra) { + return arm_t32(0xfb00u | rn, (ra << 12) | (rd << 8) | rm); +} +static inline u32 arm_umull(u32 rdlo, u32 rdhi, u32 rn, u32 rm) { + return arm_t32(0xfba0u | rn, (rdlo << 12) | (rdhi << 8) | rm); +} +static inline u32 arm_smull(u32 rdlo, u32 rdhi, u32 rn, u32 rm) { + return arm_t32(0xfb80u | rn, (rdlo << 12) | (rdhi << 8) | rm); +} + static inline u32 arm_dmb(u32 option) { return arm_t32(0xf3bfu, 0x8f50u | (option & 0xfu)); } static inline u32 arm_dsb(u32 option) { return arm_t32(0xf3bfu, 0x8f40u | (option & 0xfu)); } static inline u32 arm_isb(u32 option) { return arm_t32(0xf3bfu, 0x8f60u | (option & 0xfu)); } @@ -325,5 +347,136 @@ static inline u16 arm_yield16(void) { return (u16)0xbf10u; } static inline u16 arm_wfe16(void) { return (u16)0xbf20u; } static inline u16 arm_wfi16(void) { return (u16)0xbf30u; } static inline u16 arm_sev16(void) { return (u16)0xbf40u; } +/* --------- table branch TBB/TBH (T1) --------- */ +/* TBB [rn, rm] : hw1=0xE8D0|rn, hw2=0xF000|rm. TBH adds H=1 (<<4). */ +static inline u32 arm_tbb(u32 rn, u32 rm) { return arm_t32(0xe8d0u | rn, 0xf000u | rm); } +static inline u32 arm_tbh(u32 rn, u32 rm) { return arm_t32(0xe8d0u | rn, 0xf010u | rm); } + +/* --------- 16-bit compare-and-branch CBZ/CBNZ (T1) --------- */ +/* Encodes a forward branch of `imm6` half-words (PC-rel, +4..+130). The split + * immediate is i(bit9):imm5(bits7:3). op=1 selects CBNZ. */ +static inline u16 arm_cbz_raw(u32 op, u32 rn, u32 imm6) { + u32 i = (imm6 >> 5) & 1u, imm5 = imm6 & 0x1fu; + return (u16)(0xb100u | (op << 11) | (i << 9) | (imm5 << 3) | (rn & 7u)); +} +static inline u16 arm_cbz(u32 rn, u32 imm6) { return arm_cbz_raw(0u, rn, imm6); } +static inline u16 arm_cbnz(u32 rn, u32 imm6) { return arm_cbz_raw(1u, rn, imm6); } + +/* --------- IT (16-bit) --------- */ +/* IT{x{y{z}}} cc : firstcond in [7:4], mask in [3:0]. A single-insn IT block + * uses mask=0b1000 (0x8). Multi-insn masks encode the T/E pattern. */ +static inline u16 arm_it(u32 firstcond, u32 mask) { + return (u16)(0xbf00u | ((firstcond & 0xfu) << 4) | (mask & 0xfu)); +} + +/* ===================================================================== + * Descriptor table + disassembler/assembler lookup (defined in isa.c). + * + * Each row records (mnemonic, match, mask, fmt, flags, width). A 16-bit row + * sets ARM_FMT_W16 in flags and stores match/mask in the low 16 bits; a 32-bit + * row matches the full (hw1<<16)|hw2 word. arm32_disasm_find linear-scans and + * returns the first row whose masked bits match (first-match wins; aliases sit + * before the canonical row). arm32_asm_find maps a mnemonic to its row. + * ===================================================================== */ + +typedef enum Arm32Format { + ARM_FMT_NONE = 0, /* no operands (NOP16) */ + ARM_FMT_DP_REG, /* dp (reg): rd, rn, rm [op4 family, S flag] */ + ARM_FMT_DP_IMM, /* dp (modified-imm): rd, rn, #imm */ + ARM_FMT_MOV_IMM, /* MOV.W/MVN.W rd, #modimm (rn=1111, no rn print) */ + ARM_FMT_CMP_REG, /* CMP.W/CMN.W/TST/TEQ rn, rm (rd=1111) */ + ARM_FMT_CMP_IMM, /* CMP.W/CMN.W/TST/TEQ rn, #modimm (rd=1111) */ + ARM_FMT_MOV_REG, /* MOV.W/MVN.W rd, rm (ORR/ORN rn=1111) */ + ARM_FMT_MOVW, /* MOVW/MOVT rd, #imm16 */ + ARM_FMT_ADDW, /* ADDW/SUBW rd, rn, #imm12 */ + ARM_FMT_SHIFT_IMM, /* LSL/LSR/ASR/ROR rd, rm, #sh */ + ARM_FMT_SHIFT_REG, /* LSL/LSR/ASR/ROR rd, rn, rm */ + ARM_FMT_MUL, /* MUL rd, rn, rm */ + ARM_FMT_MLA, /* MLA/MLS rd, rn, rm, ra */ + ARM_FMT_DIV, /* SDIV/UDIV rd, rn, rm */ + ARM_FMT_MULL, /* UMULL/SMULL rdlo, rdhi, rn, rm */ + ARM_FMT_EXT, /* SXTB/SXTH/UXTB/UXTH rd, rm */ + ARM_FMT_REV, /* REV/REV16/REVSH/RBIT/CLZ rd, rm */ + ARM_FMT_BFX, /* SBFX/UBFX rd, rn, #lsb, #width */ + ARM_FMT_BFI, /* BFI rd, rn, #lsb, #width */ + ARM_FMT_BFC, /* BFC rd, #lsb, #width */ + ARM_FMT_LDST_T3, /* LDR/STR{B,H,SB,SH} rt, [rn, #imm12] */ + ARM_FMT_LDST_T4, /* LDR/STR{B,H,SB,SH} rt, [rn, #+/-imm8] */ + ARM_FMT_LDREX, /* LDREX rt, [rn{, #imm}] */ + ARM_FMT_STREX, /* STREX rd, rt, [rn{, #imm}] */ + ARM_FMT_PUSHPOP, /* PUSH.W/POP.W {reglist} */ + ARM_FMT_BARRIER, /* DMB/DSB/ISB {option} */ + ARM_FMT_TB, /* TBB/TBH [rn, rm] */ + ARM_FMT_BRANCH_T4, /* B.W <label> (24-bit) */ + ARM_FMT_BRANCH_T3, /* B<cond>.W <label> (20-bit) */ + ARM_FMT_BL, /* BL <label> */ + ARM_FMT_B16, /* B <label> (16-bit T2, 11-bit) */ + ARM_FMT_BCC16, /* B<cond> <label> (16-bit T1, 8-bit) */ + ARM_FMT_CBZ, /* CBZ/CBNZ rn, <label> */ + ARM_FMT_IT, /* IT{xyz} cc */ + ARM_FMT_MOVHI16, /* MOV (reg, high) rd, rm (16-bit) */ + ARM_FMT_BX, /* BX/BLX rm (16-bit) */ + ARM_FMT_BKPT, /* BKPT #imm8 (16-bit) */ + ARM_FMT_EXT16, /* 16-bit SXTB/SXTH/UXTB/UXTH/REV/REV16/REVSH rd, rm */ + /* ---- additional 16-bit (Thumb-1) families ---- */ + ARM_FMT_DPI8_16, /* MOVS/CMP/ADDS/SUBS rd|rn, #imm8 (rd/rn in [10:8]) */ + ARM_FMT_ADDSUB3_16, /* ADDS/SUBS rd, rn, #imm3 (rd[2:0], rn[5:3], imm[8:6]) */ + ARM_FMT_ADDSUBR_16, /* ADDS/SUBS rd, rn, rm (rd[2:0], rn[5:3], rm[8:6]) */ + ARM_FMT_SHIFTI_16, /* LSLS/LSRS/ASRS rd, rm, #imm5 */ + ARM_FMT_ALU_16, /* 16-bit data-proc reg: rdn, rm (op in [9:6]) */ + ARM_FMT_HIREG_16, /* ADD/CMP (hi reg) rdn, rm */ + ARM_FMT_LDSTI5_16, /* LDR/STR{,B,H} rt, [rn, #imm5*scale] */ + ARM_FMT_LDSTSP_16, /* LDR/STR rt, [sp, #imm8*4] (rt in [10:8]) */ + ARM_FMT_ADDSP_16, /* ADD rd, sp/pc, #imm8*4 (rd in [10:8]) */ + ARM_FMT_ADJSP_16, /* ADD/SUB sp, sp, #imm7*4 */ + ARM_FMT_PUSHPOP_16, /* PUSH/POP {reglist} (16-bit) */ +} Arm32Format; + +/* Stable decoded-opcode ids (KitDecodedInsn.opcode). Kept small: only the ops + * an emulator/decoder consumer needs to special-case carry a distinct id. */ +typedef enum Arm32DecodedOpcode { + ARM32_DEC_UNKNOWN = 0, + ARM32_DEC_BL, + ARM32_DEC_BLX, + ARM32_DEC_BX, + ARM32_DEC_B, + ARM32_DEC_BCOND, + ARM32_DEC_BKPT, + ARM32_DEC_IT, +} Arm32DecodedOpcode; + +/* ---- flags column on Arm32InsnDesc ---- */ +#define ARM_FMT_W16 0x01u /* 16-bit instruction (match/mask in low 16 bits) */ +#define ARM_ASMFL_ALIAS 0x02u /* alias row: preferred disasm spelling */ + +typedef struct Arm32InsnDesc { + Slice mnemonic; + u32 match; + u32 mask; + u8 fmt; /* Arm32Format */ + u8 flags; /* ARM_FMT_W16 / ARM_ASMFL_ALIAS */ + u8 pad[2]; +} Arm32InsnDesc; + +extern const Arm32InsnDesc arm32_insn_table[]; +extern const u32 arm32_insn_table_n; + +/* Disassembler lookup: first masked-match row, or NULL. `is16` picks the + * 16-bit vs 32-bit subset (the caller has already determined width). */ +const Arm32InsnDesc* arm32_disasm_find(u32 word, int is16); +/* Assembler lookup: mnemonic -> descriptor (non-alias preferred), or NULL. */ +const Arm32InsnDesc* arm32_asm_find(Slice mnemonic); + +/* Condition-code name for a 4-bit cond field (0..14), or "" for AL/invalid. */ +const char* arm32_cond_name(u32 cond); +/* Condition-code value for a 2-char suffix, or -1. */ +int arm32_cond_from_name(Slice s); +/* Condition field of a B<cond>.W (T3) word (disasm appends it to the mnemonic). */ +u32 arm32_branch_t3_cond(u32 w); + +/* Render operand text for `word` into `sb` using `desc->fmt`. `vaddr` is the + * instruction address (for PC-relative branch targets); 0 if unknown. */ +void arm32_print_operands(StrBuf* sb, const Arm32InsnDesc* desc, u32 word, + u64 vaddr); #endif diff --git a/test/arch/arm32_decode_test.c b/test/arch/arm32_decode_test.c @@ -0,0 +1,357 @@ +/* ARM32 (Thumb-2) structured decode test. + * + * Pins the ArchDecodeOps path: bytes encoded via the isa.h inline encoders + * decode into KitDecodedInsn records, and the formatter renders those records + * back to text. Covers each 16-bit and 32-bit family, the MOVW/MOVT immediate + * split, the BL/B.W split-immediate, IT, CBZ/TBB, and a few data-proc + ldst. + * Mirrors test/arch/rv64_decode_test.c. */ + +#include <kit/compile.h> +#include <kit/core.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "arch/arch.h" +#include "arch/arm32/isa.h" +#include "lib/kit_unit.h" + +static KitUnit g_u; +#define EXPECT(cond, ...) CU_EXPECT(&g_u, cond, __VA_ARGS__) + +static KitCompiler* new_compiler(void) { + /* arm32 is ILP32: pointers are 4 bytes (the shared helper defaults to 8). */ + KitTargetSpec t = + kit_unit_target(KIT_ARCH_ARM_32, KIT_OS_FREESTANDING, KIT_OBJ_ELF); + KitCompiler* c = NULL; + t.ptr_size = 4; + t.ptr_align = 4; + if (kit_unit_compiler_new(&g_u, t, &c) != KIT_OK || !c) { + fprintf(stderr, "compiler_new failed\n"); + exit(2); + } + return c; +} + +/* Emit a 32-bit Thumb-2 word ((hw1<<16)|hw2) as two LE half-words, hw1 first. */ +static void put_t32(unsigned char* b, size_t off, unsigned w) { + unsigned hw1 = (w >> 16) & 0xffffu, hw2 = w & 0xffffu; + b[off + 0] = (unsigned char)(hw1 & 0xffu); + b[off + 1] = (unsigned char)((hw1 >> 8) & 0xffu); + b[off + 2] = (unsigned char)(hw2 & 0xffu); + b[off + 3] = (unsigned char)((hw2 >> 8) & 0xffu); +} + +static void put_t16(unsigned char* b, size_t off, unsigned hw) { + b[off + 0] = (unsigned char)(hw & 0xffu); + b[off + 1] = (unsigned char)((hw >> 8) & 0xffu); +} + +/* Decode one instruction and assert the rendered mnemonic + an operand needle. + * `nbytes_want` is 2 or 4. */ +static void expect_text(KitCompiler* pub, const unsigned char* bytes, + size_t len, u64 pc, u8 nbytes_want, const char* mnem, + const char* op_needle, const char* what) { + Compiler* c = (Compiler*)pub; + KitDecodedInsn insn; + ArchInsnFormatter* fmt; + KitInsn text; + KitStatus st; + memset(&insn, 0, sizeof insn); + st = arch_decode_one(c, bytes, len, pc, &insn); + EXPECT(st == KIT_OK, "%s: decode_one status %d", what, (int)st); + EXPECT(insn.nbytes == nbytes_want, "%s: nbytes = %u (want %u)", what, + (unsigned)insn.nbytes, (unsigned)nbytes_want); + fmt = arch_insn_formatter_new(c); + EXPECT(fmt != NULL, "%s: formatter_new NULL", what); + if (!fmt) return; + memset(&text, 0, sizeof text); + st = arch_format_insn(fmt, &insn, &text); + EXPECT(st == KIT_OK, "%s: format status %d", what, (int)st); + EXPECT(kit_slice_eq_cstr(text.mnemonic, mnem), "%s: mnemonic = '%.*s' want '%s'", + what, KIT_SLICE_ARG(text.mnemonic), mnem); + if (op_needle && op_needle[0]) { + EXPECT(text.operands.s && strstr(text.operands.s, op_needle), + "%s: operands '%.*s' missing '%s'", what, KIT_SLICE_ARG(text.operands), + op_needle); + } + arch_insn_formatter_free(fmt); +} + +static void decode_movw_movt(KitCompiler* pub) { + unsigned char b[4]; + put_t32(b, 0, arm_movw(ARM_R0, 0x1234)); + expect_text(pub, b, 4, 0x1000, 4, "movw", "r0", "movw imm"); + expect_text(pub, b, 4, 0x1000, 4, "movw", "4660", "movw value"); /* 0x1234 */ + put_t32(b, 0, arm_movt(ARM_R0, 0xabcd)); + expect_text(pub, b, 4, 0x1000, 4, "movt", "r0", "movt"); + + /* Confirm the operand-decode path recovers the immediate. */ + { + Compiler* c = (Compiler*)pub; + KitDecodedInsn insn; + put_t32(b, 0, arm_movw(ARM_R3, 0xbeef)); + memset(&insn, 0, sizeof insn); + EXPECT(arch_decode_one(c, b, 4, 0, &insn) == KIT_OK, "movw decode"); + EXPECT(insn.noperands == 2, "movw noperands = %u", (unsigned)insn.noperands); + EXPECT(insn.operands[0].kind == KIT_DECOP_REG && insn.operands[0].reg == 3, + "movw rd"); + EXPECT(insn.operands[1].kind == KIT_DECOP_IMM && + insn.operands[1].imm == 0xbeef, + "movw imm = 0x%llx", (unsigned long long)insn.operands[1].imm); + } +} + +static void decode_dp(KitCompiler* pub) { + unsigned char b[4]; + put_t32(b, 0, arm_add_reg(ARM_R0, ARM_R1, ARM_R2)); + expect_text(pub, b, 4, 0, 4, "add.w", "r0, r1, r2", "add.w reg"); + put_t32(b, 0, arm_sub_reg(ARM_R3, ARM_R4, ARM_R5)); + expect_text(pub, b, 4, 0, 4, "sub.w", "r3, r4, r5", "sub.w reg"); + put_t32(b, 0, arm_orr_reg(ARM_R0, ARM_R1, ARM_R2)); + expect_text(pub, b, 4, 0, 4, "orr.w", "r0, r1, r2", "orr.w reg"); + { + u32 out12; + EXPECT(thumb_expand_imm_encode(0xff, &out12), "encode 0xff modimm"); + put_t32(b, 0, arm_dp_imm(8u, 0u, ARM_R0, ARM_R1, out12)); /* add.w r0,r1,#255 */ + expect_text(pub, b, 4, 0, 4, "add.w", "#255", "add.w imm"); + } + put_t32(b, 0, arm_mov_reg(ARM_R0, ARM_R1)); + expect_text(pub, b, 4, 0, 4, "mov.w", "r0, r1", "mov.w reg"); + put_t32(b, 0, arm_cmp_reg(ARM_R2, ARM_R3)); + expect_text(pub, b, 4, 0, 4, "cmp.w", "r2, r3", "cmp.w reg"); +} + +static void decode_mul_div_ext(KitCompiler* pub) { + unsigned char b[4]; + put_t32(b, 0, arm_mul(ARM_R0, ARM_R1, ARM_R2)); + expect_text(pub, b, 4, 0, 4, "mul", "r0, r1, r2", "mul"); + put_t32(b, 0, arm_sdiv(ARM_R0, ARM_R1, ARM_R2)); + expect_text(pub, b, 4, 0, 4, "sdiv", "r0, r1, r2", "sdiv"); + put_t32(b, 0, arm_udiv(ARM_R3, ARM_R4, ARM_R5)); + expect_text(pub, b, 4, 0, 4, "udiv", "r3, r4, r5", "udiv"); + put_t32(b, 0, arm_umull(ARM_R0, ARM_R1, ARM_R2, ARM_R3)); + expect_text(pub, b, 4, 0, 4, "umull", "r0, r1, r2, r3", "umull"); + put_t32(b, 0, arm_sxtb(ARM_R0, ARM_R1)); + expect_text(pub, b, 4, 0, 4, "sxtb", "r0, r1", "sxtb"); + put_t32(b, 0, arm_uxth(ARM_R2, ARM_R3)); + expect_text(pub, b, 4, 0, 4, "uxth", "r2, r3", "uxth"); +} + +static void decode_rev_clz_bitfield(KitCompiler* pub) { + unsigned char b[4]; + /* kit emits the 16-bit REV (arm_rev returns a u16, 0xba__); the disassembler + * decodes both the 16-bit (T1) and 32-bit (T2) forms via separate table rows. */ + put_t16(b, 0, arm_rev(ARM_R0, ARM_R1)); + expect_text(pub, b, 2, 0, 2, "rev", "r0, r1", "rev"); + put_t32(b, 0, arm_clz(ARM_R2, ARM_R3)); + expect_text(pub, b, 4, 0, 4, "clz", "r2, r3", "clz"); + put_t32(b, 0, arm_bfi(ARM_R0, ARM_R1, 4, 8)); + expect_text(pub, b, 4, 0, 4, "bfi", "r0, r1, #4, #8", "bfi"); + put_t32(b, 0, arm_ubfx(ARM_R0, ARM_R1, 4, 8)); + expect_text(pub, b, 4, 0, 4, "ubfx", "r0, r1, #4, #8", "ubfx"); + put_t32(b, 0, arm_sbfx(ARM_R2, ARM_R3, 0, 5)); + expect_text(pub, b, 4, 0, 4, "sbfx", "r2, r3, #0, #5", "sbfx"); +} + +static void decode_ldst(KitCompiler* pub) { + unsigned char b[4]; + put_t32(b, 0, arm_ldr_imm(ARM_R0, ARM_R1, 4)); + expect_text(pub, b, 4, 0, 4, "ldr.w", "[r1, #4]", "ldr.w T3"); + put_t32(b, 0, arm_str_imm(ARM_R2, ARM_R3, 8)); + expect_text(pub, b, 4, 0, 4, "str.w", "[r3, #8]", "str.w T3"); + put_t32(b, 0, arm_ldrb_imm(ARM_R0, ARM_R1, 1)); + expect_text(pub, b, 4, 0, 4, "ldrb.w", "[r1, #1]", "ldrb.w T3"); + /* T4 negative offset. */ + put_t32(b, 0, arm_ldst_t4(0xf850u, ARM_R0, ARM_R1, 4, 0)); + expect_text(pub, b, 4, 0, 4, "ldr.w", "[r1, #-4]", "ldr.w T4 -imm8"); + /* Structured operand path on the T3 load. */ + { + Compiler* c = (Compiler*)pub; + KitDecodedInsn insn; + put_t32(b, 0, arm_ldr_imm(ARM_R5, ARM_R6, 12)); + memset(&insn, 0, sizeof insn); + EXPECT(arch_decode_one(c, b, 4, 0, &insn) == KIT_OK, "ldr decode"); + EXPECT((insn.flags & KIT_DECODE_MEMORY) != 0, "ldr is memory"); + EXPECT(insn.operands[1].kind == KIT_DECOP_MEM && + insn.operands[1].reg == 6 && insn.operands[1].imm == 12, + "ldr mem operand"); + } +} + +static void decode_pushpop(KitCompiler* pub) { + unsigned char b[4]; + put_t32(b, 0, arm_push_w((1u << 4) | (1u << 14))); /* push {r4, lr} */ + expect_text(pub, b, 4, 0, 4, "push.w", "r4", "push.w"); + expect_text(pub, b, 4, 0, 4, "push.w", "lr", "push.w lr"); + put_t32(b, 0, arm_pop_w((1u << 4) | (1u << 15))); /* pop {r4, pc} */ + expect_text(pub, b, 4, 0, 4, "pop.w", "pc", "pop.w"); +} + +static void decode_branches(KitCompiler* pub) { + unsigned char b[4]; + /* BL +8: the 24-bit split immediate {S,I1,I2,imm10,imm11} encodes the byte + * offset (target - pc - 4). For +8 with S=0: I1=I2=1 (so J1=J2=1), imm11=2. */ + put_t32(b, 0, 0xf000f802u); + expect_text(pub, b, 4, 0x1000, 4, "bl", "0x1008", "bl +8"); + + /* B.W +8 (T4): hw2[15:14]=10, hw2[12]=1, J1=J2=1, imm11=2. */ + put_t32(b, 0, 0xf000b802u); + expect_text(pub, b, 4, 0x1000, 4, "b.w", "0x1008", "b.w +8"); + + /* B<cond>.W +8 (T3): cond eq in hw1[9:6]; hw2[15:14]=10, hw2[12]=0. */ + put_t32(b, 0, 0xf0008002u); + expect_text(pub, b, 4, 0x1000, 4, "beq.w", "0x1008", "beq.w +8"); + + /* 16-bit Bcc (T1): 0xDxyy, cond=NE (1), imm8=2 -> off = 4 + 4 = 8. */ + put_t16(b, 0, 0xd102u); + expect_text(pub, b, 2, 0x1000, 2, "bne", "0x1008", "bne 16-bit"); + + /* 16-bit B (T2): 0xE002 -> off = 4 + 4 = 8. */ + put_t16(b, 0, 0xe002u); + expect_text(pub, b, 2, 0x1000, 2, "b", "0x1008", "b 16-bit"); + + /* BX lr / BLX r3. */ + put_t16(b, 0, arm_bx(ARM_LR)); + expect_text(pub, b, 2, 0, 2, "bx", "lr", "bx lr"); + put_t16(b, 0, arm_blx_reg(ARM_R3)); + expect_text(pub, b, 2, 0, 2, "blx", "r3", "blx r3"); +} + +static void decode_cbz_tbb(KitCompiler* pub) { + unsigned char b[4]; + /* CBZ r0, +8: imm6 = (8-4)/2 = 2. */ + put_t16(b, 0, arm_cbz(ARM_R0, 2)); + expect_text(pub, b, 2, 0x1000, 2, "cbz", "r0", "cbz r0"); + expect_text(pub, b, 2, 0x1000, 2, "cbz", "0x1008", "cbz target"); + put_t16(b, 0, arm_cbnz(ARM_R1, 2)); + expect_text(pub, b, 2, 0x1000, 2, "cbnz", "r1", "cbnz r1"); + /* TBB [r0, r1]. */ + put_t32(b, 0, arm_tbb(ARM_R0, ARM_R1)); + expect_text(pub, b, 4, 0, 4, "tbb", "[r0, r1]", "tbb"); + put_t32(b, 0, arm_tbh(ARM_R2, ARM_R3)); + expect_text(pub, b, 4, 0, 4, "tbh", "[r2, r3", "tbh"); +} + +static void decode_misc16(KitCompiler* pub) { + unsigned char b[4]; + put_t16(b, 0, arm_nop16()); + expect_text(pub, b, 2, 0, 2, "nop", "", "nop"); + put_t16(b, 0, arm_bkpt(0xab)); + expect_text(pub, b, 2, 0, 2, "bkpt", "#171", "bkpt"); + put_t16(b, 0, arm_mov_hi(ARM_R7, ARM_SP)); /* mov r7, sp */ + expect_text(pub, b, 2, 0, 2, "mov", "r7, sp", "mov r7, sp"); +} + +static void decode_barriers(KitCompiler* pub) { + unsigned char b[4]; + put_t32(b, 0, arm_dmb(0xf)); + expect_text(pub, b, 4, 0, 4, "dmb", "sy", "dmb sy"); + put_t32(b, 0, arm_dsb(0xf)); + expect_text(pub, b, 4, 0, 4, "dsb", "sy", "dsb sy"); + put_t32(b, 0, arm_isb(0xf)); + expect_text(pub, b, 4, 0, 4, "isb", "sy", "isb sy"); +} + +static void decode_ldrex_strex(KitCompiler* pub) { + unsigned char b[4]; + put_t32(b, 0, arm_ldrex(ARM_R0, ARM_R1, 0)); + expect_text(pub, b, 4, 0, 4, "ldrex", "[r1]", "ldrex"); + put_t32(b, 0, arm_strex(ARM_R0, ARM_R1, ARM_R2, 0)); + expect_text(pub, b, 4, 0, 4, "strex", "r0, r1, [r2]", "strex"); +} + +/* The IT block renders its firstcond suffix on the following instruction. */ +static void decode_it_block(KitCompiler* pub) { + Compiler* c = (Compiler*)pub; + unsigned char b[8]; + KitDecodedInsn insts[2]; + ArchInsnFormatter* fmt; + KitInsn t0, t1; + u32 n = 0; + u32 out12; + + put_t16(b, 0, arm_it(ARM_CC_EQ, 0x8)); /* it eq */ + thumb_expand_imm_encode(1, &out12); + put_t32(b, 2, arm_mov_imm(ARM_R0, out12)); /* mov.w r0, #1 -> moveq */ + + memset(insts, 0, sizeof insts); + EXPECT(arch_decode_block(c, b, sizeof b, 0x2000, insts, 2, &n) == KIT_OK, + "it block decode_block"); + EXPECT(n == 2, "it block n = %u", (unsigned)n); + EXPECT(insts[0].nbytes == 2, "it nbytes = %u", (unsigned)insts[0].nbytes); + EXPECT(insts[0].opcode == ARM32_DEC_IT, "it opcode = %u", + (unsigned)insts[0].opcode); + + /* Format both through one formatter so the IT-state threads across. */ + fmt = arch_insn_formatter_new(c); + EXPECT(fmt != NULL, "it formatter"); + if (!fmt) return; + memset(&t0, 0, sizeof t0); + memset(&t1, 0, sizeof t1); + EXPECT(arch_format_insn(fmt, &insts[0], &t0) == KIT_OK, "format it"); + EXPECT(kit_slice_eq_cstr(t0.mnemonic, "it"), "it mnemonic = '%.*s'", + KIT_SLICE_ARG(t0.mnemonic)); + EXPECT(t0.operands.s && strstr(t0.operands.s, "eq"), "it operand 'eq': %.*s", + KIT_SLICE_ARG(t0.operands)); + EXPECT(arch_format_insn(fmt, &insts[1], &t1) == KIT_OK, "format moveq"); + EXPECT(kit_slice_eq_cstr(t1.mnemonic, "moveq.w"), + "conditional mnemonic = '%.*s' (want moveq.w)", + KIT_SLICE_ARG(t1.mnemonic)); + arch_insn_formatter_free(fmt); +} + +/* decode_block stops at a terminator (a B.W). */ +static void decode_block_terminates(KitCompiler* pub) { + Compiler* c = (Compiler*)pub; + unsigned char b[12]; + KitDecodedInsn insts[4]; + u32 n = 0; + put_t32(b, 0, arm_add_reg(ARM_R0, ARM_R1, ARM_R2)); + put_t32(b, 4, 0xf000b802u); /* b.w +8 (terminator) */ + put_t32(b, 8, arm_add_reg(ARM_R3, ARM_R4, ARM_R5)); + memset(insts, 0, sizeof insts); + EXPECT(arch_decode_block(c, b, sizeof b, 0, insts, 4, &n) == KIT_OK, + "block decode"); + EXPECT(n == 2, "block stops at b.w: n = %u", (unsigned)n); + EXPECT((insts[1].flags & KIT_DECODE_TERMINATOR) != 0, "b.w terminates"); +} + +/* Undecodable bytes fall back to .inst / .hword. */ +static void decode_unknown_fallback(KitCompiler* pub) { + unsigned char b[4]; + /* A 32-bit-marked but undefined word (top5 = 11111, garbage body). */ + put_t32(b, 0, 0xfff0ffffu); + expect_text(pub, b, 4, 0, 4, ".inst", "0x", ".inst fallback"); + /* A 16-bit undefined halfword (0xDExx is UDF). */ + put_t16(b, 0, 0xde01u); + /* 0xDE.. matches B<cond> with cond=0xE (AL) which is actually UDF; our table + * maps cond 0xE to "" so it renders as a bare "b" — acceptable. We instead + * use a clearly-unmapped 16-bit value. */ + put_t16(b, 0, 0xb600u); /* CPS/SETEND space, not in the table */ + expect_text(pub, b, 2, 0, 2, ".hword", "0x", ".hword fallback"); +} + +int main(void) { + KitCompiler* c; + kit_unit_init(&g_u); + c = new_compiler(); + decode_movw_movt(c); + decode_dp(c); + decode_mul_div_ext(c); + decode_rev_clz_bitfield(c); + decode_ldst(c); + decode_pushpop(c); + decode_branches(c); + decode_cbz_tbb(c); + decode_misc16(c); + decode_barriers(c); + decode_ldrex_strex(c); + decode_it_block(c); + decode_block_terminates(c); + decode_unknown_fallback(c); + kit_compiler_free(c); + kit_unit_summary(&g_u, "arm32_decode_test"); + return kit_unit_status(&g_u); +}