kit

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

commit 51a0605d7da6f4b5bdb045921c315598ab57ec06
parent a1dca02e7fb38956632f2c76acaff5f2b0fa89eb
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed, 17 Jun 2026 10:48:39 -0700

arm32 disasm: render shifted-MOV (lsl/lsr/asr/ror/rrx) + MVN shift suffix

Diffstat:
Msrc/arch/arm32/disasm.c | 12++++++++++++
Msrc/arch/arm32/isa.c | 52++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/arch/arm32/isa.h | 10++++++++++
Mtest/arch/arm32_decode_test.c | 34++++++++++++++++++++++++++++++++++
4 files changed, 106 insertions(+), 2 deletions(-)

diff --git a/src/arch/arm32/disasm.c b/src/arch/arm32/disasm.c @@ -327,6 +327,18 @@ static void arm32_render_mnemonic(Arm32InsnFormatter* f, const Arm32InsnDesc* d, strbuf_putc(&f->mnem, (cc[k] == cc[0]) ? 't' : 'e'); return; } + /* Shifted-MOV (ORR rn=1111) aliases to the shift mnemonic (lsl/lsr/asr/ror.w + * or rrx); the MVN (ORN) form keeps `mvn.w` and renders the shift as an + * operand suffix instead. Substitute the alias as the base mnemonic for the + * MOV form, then fall through to the shared IT-cond / `.w` suffix logic + * (`rrx` carries no `.w`, so that logic leaves it untouched). */ + if ((Arm32Format)d->fmt == ARM_FMT_MOV_REG) { + u32 op4 = (word >> 21) & 0xfu; /* hw1[8:5]: 2 = MOV (ORR), 3 = MVN (ORN). */ + if (op4 == 2u) { + const char* alias = arm32_mov_shift_alias(word, NULL, NULL); + if (alias) mn = slice_from_cstr(alias); + } + } /* Base mnemonic, with optional IT condition suffix inserted before ".w". */ if (in_it) { const char* ccn = arm32_cond_name(it_cond); diff --git a/src/arch/arm32/isa.c b/src/arch/arm32/isa.c @@ -467,6 +467,10 @@ static u32 f_modimm12(u32 w) { return (i << 11) | (imm3 << 8) | imm8; } +/* Shift-type names for the DP-shift / MOV-shift / MVN-shift suffix + * (index = hw2[5:4]). */ +static const char* const ARM_SHIFT_NAMES[4] = {"lsl", "lsr", "asr", "ror"}; + static void print_dp_reg(StrBuf* sb, u32 w) { p_reg(sb, f_rd(w)); p_sep(sb); @@ -478,10 +482,9 @@ static void print_dp_reg(StrBuf* sb, u32 w) { 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_puts(sb, ARM_SHIFT_NAMES[type]); strbuf_putc(sb, ' '); p_imm(sb, (i64)(sh == 0u && type != 0u ? 32u : sh)); } @@ -502,10 +505,55 @@ static void print_mov_imm(StrBuf* sb, u32 w) { p_imm(sb, (i64)(i32)thumb_expand_imm_decode(f_modimm12(w))); } +const char* arm32_mov_shift_alias(u32 word, u32* type_out, u32* amount_out) { + /* hw2[14:12] = imm3, hw2[7:6] = imm2, hw2[5:4] = type. */ + u32 imm3 = (f_hw2(word) >> 12) & 7u, imm2 = (f_hw2(word) >> 6) & 3u; + u32 type = (f_hw2(word) >> 4) & 3u; + u32 amount = (imm3 << 2) | imm2; + if (type_out) *type_out = type; + if (amount_out) *amount_out = amount; + if (type == 0u && amount == 0u) return NULL; /* plain mov.w rd, rm. */ + switch (type) { + case 0u: return "lsl.w"; /* amount != 0 here. */ + case 1u: + if (amount == 0u) amount = 32u; /* LSR #0 means #32. */ + if (amount_out) *amount_out = amount; + return "lsr.w"; + case 2u: + if (amount == 0u) amount = 32u; /* ASR #0 means #32. */ + if (amount_out) *amount_out = amount; + return "asr.w"; + default: /* type == 3 (ROR). */ + return (amount == 0u) ? "rrx" : "ror.w"; + } +} + +/* MOV (ORR) form. op4 == 3 is MVN (ORN), which keeps its own mnemonic and + * renders the shift as a `, <sh> #n` suffix; op4 == 2 is MOV, which is aliased + * to the shift mnemonic (so here it prints just the operands). */ static void print_mov_reg(StrBuf* sb, u32 w) { + u32 op4 = (f_hw1(w) >> 5) & 0xfu; + u32 type = 0u, amount = 0u; + const char* alias = arm32_mov_shift_alias(w, &type, &amount); p_reg(sb, f_rd(w)); p_sep(sb); p_reg(sb, f_rm(w)); + if (op4 == 3u) { + /* MVN: append the shift suffix when present (mnemonic stays mvn.w). */ + if (alias) { + p_sep(sb); + strbuf_puts(sb, ARM_SHIFT_NAMES[type]); + strbuf_putc(sb, ' '); + p_imm(sb, (i64)amount); + } + return; + } + /* MOV (op4 == 2): the mnemonic carries the shift; append the amount unless + * the alias is a plain mov.w (NULL) or rrx (ROR #0 -> two operands only). */ + if (alias && !(type == 3u && amount == 0u)) { + p_sep(sb); + p_imm(sb, (i64)amount); + } } static void print_cmp_reg(StrBuf* sb, u32 w) { diff --git a/src/arch/arm32/isa.h b/src/arch/arm32/isa.h @@ -486,6 +486,16 @@ 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); +/* Shifted-MOV (ORR/ORN rn=1111, ARM_FMT_MOV_REG) shift decode. Returns the + * shift mnemonic the MOV form aliases to ("lsl.w"/"lsr.w"/"asr.w"/"ror.w", or + * "rrx" for ROR #0), or NULL when there is no shift (a plain `mov.w`). Decodes + * imm3:imm2 (hw2[14:12]/[7:6]) and type (hw2[5:4]); applies the LSR/ASR amount + * 0 -> 32 and ROR amount 0 -> rrx rules. *type_out gets the raw 2-bit shift + * type, *amount_out the rendered amount (undefined/unused for rrx). Both out + * pointers may be NULL. The MVN form is NOT aliased; callers reuse type_out/ + * amount_out to append a `, <sh> #n` suffix. */ +const char* arm32_mov_shift_alias(u32 word, u32* type_out, u32* amount_out); + /* 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, diff --git a/test/arch/arm32_decode_test.c b/test/arch/arm32_decode_test.c @@ -123,6 +123,39 @@ static void decode_dp(KitCompiler* pub) { expect_text(pub, b, 4, 0, 4, "cmp.w", "r2, r3", "cmp.w reg"); } +/* The Thumb-2 shifted-MOV family (0xea4f....) is the ORR-based MOV-with-shift; + * it disassembles (per llvm-objdump --triple=thumbv7em) to the shift mnemonic + * (lsl/lsr/asr/ror, or rrx for ROR #0) — except a plain LSL #0, which stays + * `mov.w`. The MVN-with-shift form (0xea6f....) keeps its `mvn.w` mnemonic and + * appends a `, <sh> #n` suffix. No direct encoder, so raw words via put_t32. */ +static void decode_shifts(KitCompiler* pub) { + unsigned char b[4]; + /* LSL #0 = plain mov.w (regression guard). */ + put_t32(b, 0, 0xea4f0001u); + expect_text(pub, b, 4, 0, 4, "mov.w", "r0, r1", "mov-shift lsl#0 = mov.w"); + /* LSL #3 -> lsl.w. */ + put_t32(b, 0, 0xea4f00c1u); + expect_text(pub, b, 4, 0, 4, "lsl.w", "r0, r1, #3", "mov-shift lsl#3"); + /* LSR #0 means #32. */ + put_t32(b, 0, 0xea4f0011u); + expect_text(pub, b, 4, 0, 4, "lsr.w", "r0, r1, #32", "mov-shift lsr#0->32"); + /* ASR #0 means #32. */ + put_t32(b, 0, 0xea4f0021u); + expect_text(pub, b, 4, 0, 4, "asr.w", "r0, r1, #32", "mov-shift asr#0->32"); + /* ROR #5. */ + put_t32(b, 0, 0xea4f1071u); + expect_text(pub, b, 4, 0, 4, "ror.w", "r0, r1, #5", "mov-shift ror#5"); + /* ROR #0 -> rrx (two operands, no amount, no .w). */ + put_t32(b, 0, 0xea4f0031u); + expect_text(pub, b, 4, 0, 4, "rrx", "r0, r1", "mov-shift ror#0 = rrx"); + /* MVN LSL #3 keeps mvn.w + appends the shift suffix. */ + put_t32(b, 0, 0xea6f00c1u); + expect_text(pub, b, 4, 0, 4, "mvn.w", "r0, r1, lsl #3", "mvn-shift lsl#3"); + /* MVN LSL #0 = plain mvn.w (regression guard). */ + put_t32(b, 0, 0xea6f0001u); + expect_text(pub, b, 4, 0, 4, "mvn.w", "r0, r1", "mvn lsl#0 = mvn.w"); +} + static void decode_mul_div_ext(KitCompiler* pub) { unsigned char b[4]; put_t32(b, 0, arm_mul(ARM_R0, ARM_R1, ARM_R2)); @@ -339,6 +372,7 @@ int main(void) { c = new_compiler(); decode_movw_movt(c); decode_dp(c); + decode_shifts(c); decode_mul_div_ext(c); decode_rev_clz_bitfield(c); decode_ldst(c);