kit

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

isa.h (30019B)


      1 /* ARM32 Thumb-2 instruction encoders (header-only, inline).
      2  *
      3  * Phase 1 (the walking skeleton) needs only the encoders the -O0 native
      4  * backend emits for the smoke set: MOVW/MOVT + modified-immediate constants,
      5  * the data-processing reg/imm families, multiply/divide, shifts, extends,
      6  * compare, branches, BL, push/pop, and LDR/STR. A descriptor-driven table +
      7  * disassembler is a Phase-2 deliverable.
      8  *
      9  * Every 32-bit Thumb-2 instruction is returned as a single u32 with the FIRST
     10  * half-word (hw1) in bits [31:16] and the SECOND (hw2) in [15:0]; native.c's
     11  * arm_emit_t32 writes them as two little-endian half-words (hw1 first). 16-bit
     12  * instructions are returned as a u16 and emitted via arm_emit_t16. */
     13 #ifndef KIT_ARCH_ARM32_ISA_H
     14 #define KIT_ARCH_ARM32_ISA_H
     15 
     16 #include "core/core.h"
     17 #include "core/slice.h"
     18 #include "core/strbuf.h"
     19 
     20 /* Core register numbers (AAPCS roles). */
     21 enum {
     22   ARM_R0 = 0,
     23   ARM_R1 = 1,
     24   ARM_R2 = 2,
     25   ARM_R3 = 3,
     26   ARM_R4 = 4,
     27   ARM_R5 = 5,
     28   ARM_R6 = 6,
     29   ARM_R7 = 7,
     30   ARM_R8 = 8,
     31   ARM_R9 = 9,
     32   ARM_R10 = 10,
     33   ARM_R11 = 11,
     34   /* The Thumb frame pointer is r7 in kit's ABI; native.c owns `#define ARM_FP
     35    * 7u`. No ARM_FP enumerator here — it would shadow that macro and mislead. */
     36   ARM_R12 = 12,
     37   ARM_IP = 12, /* intra-procedure scratch */
     38   ARM_SP = 13,
     39   ARM_LR = 14,
     40   ARM_PC = 15,
     41 };
     42 
     43 /* Thumb condition codes (for B<cond> / IT). */
     44 enum {
     45   ARM_CC_EQ = 0x0,
     46   ARM_CC_NE = 0x1,
     47   ARM_CC_CS = 0x2, /* HS, unsigned >= */
     48   ARM_CC_CC = 0x3, /* LO, unsigned <  */
     49   ARM_CC_MI = 0x4,
     50   ARM_CC_PL = 0x5,
     51   ARM_CC_VS = 0x6,
     52   ARM_CC_VC = 0x7,
     53   ARM_CC_HI = 0x8, /* unsigned >  */
     54   ARM_CC_LS = 0x9, /* unsigned <= */
     55   ARM_CC_GE = 0xa,
     56   ARM_CC_LT = 0xb,
     57   ARM_CC_GT = 0xc,
     58   ARM_CC_LE = 0xd,
     59   ARM_CC_AL = 0xe,
     60 };
     61 
     62 static inline u32 arm_t32(u32 hw1, u32 hw2) {
     63   return ((hw1 & 0xffffu) << 16) | (hw2 & 0xffffu);
     64 }
     65 
     66 /* --------- ThumbExpandImm (rotated-8-bit modified immediate) --------- */
     67 /* Encode `v` as a 12-bit i:imm3:imm8 modified immediate. Returns 1 + sets
     68  * *out12 on success, 0 if `v` is not representable (caller materializes it). */
     69 static inline int thumb_expand_imm_encode(u32 v, u32* out12) {
     70   u32 b0 = v & 0xffu, b1 = (v >> 8) & 0xffu, b2 = (v >> 16) & 0xffu,
     71       b3 = (v >> 24) & 0xffu;
     72   u32 rot;
     73   if (b1 == 0 && b2 == 0 && b3 == 0) {
     74     *out12 = b0;
     75     return 1; /* 0x000000XY */
     76   }
     77   if (b0 == b2 && b0 != 0 && b1 == 0 && b3 == 0) {
     78     *out12 = 0x100u | b0;
     79     return 1; /* 0x00XY00XY */
     80   }
     81   if (b1 == b3 && b1 != 0 && b0 == 0 && b2 == 0) {
     82     *out12 = 0x200u | b1;
     83     return 1; /* 0xXY00XY00 */
     84   }
     85   if (b0 == b1 && b1 == b2 && b2 == b3 && b0 != 0) {
     86     *out12 = 0x300u | b0;
     87     return 1; /* 0xXYXYXYXY */
     88   }
     89   for (rot = 8; rot < 32; ++rot) {
     90     u32 base = (v << rot) | (v >> (32u - rot)); /* rol(v, rot) == base8 */
     91     if (base <= 0xffu && (base & 0x80u)) {
     92       *out12 = (rot << 7) | (base & 0x7fu);
     93       return 1;
     94     }
     95   }
     96   return 0;
     97 }
     98 
     99 /* --------- constant / address materialization --------- */
    100 static inline u32 arm_movw(u32 rd, u32 imm16) {
    101   u32 i = (imm16 >> 11) & 1u, imm4 = (imm16 >> 12) & 0xfu, imm3 = (imm16 >> 8) & 7u,
    102       imm8 = imm16 & 0xffu;
    103   return arm_t32(0xf240u | (i << 10) | imm4, (imm3 << 12) | (rd << 8) | imm8);
    104 }
    105 static inline u32 arm_movt(u32 rd, u32 imm16) {
    106   u32 i = (imm16 >> 11) & 1u, imm4 = (imm16 >> 12) & 0xfu, imm3 = (imm16 >> 8) & 7u,
    107       imm8 = imm16 & 0xffu;
    108   return arm_t32(0xf2c0u | (i << 10) | imm4, (imm3 << 12) | (rd << 8) | imm8);
    109 }
    110 /* MOV.W rd, #modimm (out12 from thumb_expand_imm_encode). */
    111 static inline u32 arm_mov_imm(u32 rd, u32 out12) {
    112   u32 i = (out12 >> 11) & 1u, imm3 = (out12 >> 8) & 7u, imm8 = out12 & 0xffu;
    113   return arm_t32(0xf04fu | (i << 10), (imm3 << 12) | (rd << 8) | imm8);
    114 }
    115 /* MVN.W rd, #modimm. */
    116 static inline u32 arm_mvn_imm(u32 rd, u32 out12) {
    117   u32 i = (out12 >> 11) & 1u, imm3 = (out12 >> 8) & 7u, imm8 = out12 & 0xffu;
    118   return arm_t32(0xf06fu | (i << 10), (imm3 << 12) | (rd << 8) | imm8);
    119 }
    120 
    121 /* --------- data processing (register, no shift) --------- */
    122 /* op4: AND=0,BIC=1,ORR=2,ORN=3,EOR=4,ADD=8,ADC=10,SBC=11,SUB=13,RSB=14. */
    123 static inline u32 arm_dp_reg(u32 op4, u32 setflags, u32 rd, u32 rn, u32 rm) {
    124   return arm_t32(0xea00u | (op4 << 5) | (setflags << 4) | rn,
    125                  (rd << 8) | rm);
    126 }
    127 static inline u32 arm_add_reg(u32 rd, u32 rn, u32 rm) { return arm_dp_reg(8u, 0u, rd, rn, rm); }
    128 /* ADD.W rd, rn, rm, LSL #sh (T3, no flags): scaled-index base+offset for
    129  * indexed addressing. type=00 (LSL); sh in imm3:imm2. */
    130 static inline u32 arm_add_reg_lsl(u32 rd, u32 rn, u32 rm, u32 sh) {
    131   u32 imm3 = (sh >> 2) & 7u, imm2 = sh & 3u;
    132   return arm_t32(0xeb00u | rn, (imm3 << 12) | (rd << 8) | (imm2 << 6) | rm);
    133 }
    134 static inline u32 arm_adds_reg(u32 rd, u32 rn, u32 rm) { return arm_dp_reg(8u, 1u, rd, rn, rm); }
    135 static inline u32 arm_sub_reg(u32 rd, u32 rn, u32 rm) { return arm_dp_reg(13u, 0u, rd, rn, rm); }
    136 static inline u32 arm_and_reg(u32 rd, u32 rn, u32 rm) { return arm_dp_reg(0u, 0u, rd, rn, rm); }
    137 static inline u32 arm_orr_reg(u32 rd, u32 rn, u32 rm) { return arm_dp_reg(2u, 0u, rd, rn, rm); }
    138 static inline u32 arm_eor_reg(u32 rd, u32 rn, u32 rm) { return arm_dp_reg(4u, 0u, rd, rn, rm); }
    139 /* SUB.W/AND.W/ORR.W/EOR.W rd, rn, rm, LSL #sh (T2, no flags) — the shifted-
    140  * register operand fold (mirror of arm_add_reg_lsl above; same imm3:imm2 = sh,
    141  * type=00 LSL). The optimizer's L7 pass restricts sh to 1..4, so it always fits
    142  * the 5-bit field. These are the only DP ops binop_takes_shifted_rhs admits. */
    143 static inline u32 arm_sub_reg_lsl(u32 rd, u32 rn, u32 rm, u32 sh) {
    144   u32 imm3 = (sh >> 2) & 7u, imm2 = sh & 3u;
    145   return arm_t32(0xeba0u | rn, (imm3 << 12) | (rd << 8) | (imm2 << 6) | rm);
    146 }
    147 static inline u32 arm_and_reg_lsl(u32 rd, u32 rn, u32 rm, u32 sh) {
    148   u32 imm3 = (sh >> 2) & 7u, imm2 = sh & 3u;
    149   return arm_t32(0xea00u | rn, (imm3 << 12) | (rd << 8) | (imm2 << 6) | rm);
    150 }
    151 static inline u32 arm_orr_reg_lsl(u32 rd, u32 rn, u32 rm, u32 sh) {
    152   u32 imm3 = (sh >> 2) & 7u, imm2 = sh & 3u;
    153   return arm_t32(0xea40u | rn, (imm3 << 12) | (rd << 8) | (imm2 << 6) | rm);
    154 }
    155 static inline u32 arm_eor_reg_lsl(u32 rd, u32 rn, u32 rm, u32 sh) {
    156   u32 imm3 = (sh >> 2) & 7u, imm2 = sh & 3u;
    157   return arm_t32(0xea80u | rn, (imm3 << 12) | (rd << 8) | (imm2 << 6) | rm);
    158 }
    159 /* MOV.W rd, rm (ORR rd, 1111, rm). */
    160 static inline u32 arm_mov_reg(u32 rd, u32 rm) { return arm_dp_reg(2u, 0u, rd, 0xfu, rm); }
    161 /* MVN.W rd, rm (ORN rd, 1111, rm). */
    162 static inline u32 arm_mvn_reg(u32 rd, u32 rm) { return arm_dp_reg(3u, 0u, rd, 0xfu, rm); }
    163 /* CMP.W rn, rm (SUB S=1, Rd=1111). */
    164 static inline u32 arm_cmp_reg(u32 rn, u32 rm) { return arm_dp_reg(13u, 1u, 0xfu, rn, rm); }
    165 
    166 /* --------- data processing (modified immediate) --------- */
    167 static inline u32 arm_dp_imm(u32 op4, u32 setflags, u32 rd, u32 rn, u32 out12) {
    168   u32 i = (out12 >> 11) & 1u, imm3 = (out12 >> 8) & 7u, imm8 = out12 & 0xffu;
    169   return arm_t32(0xf000u | (i << 10) | (op4 << 5) | (setflags << 4) | rn,
    170                  (imm3 << 12) | (rd << 8) | imm8);
    171 }
    172 static inline u32 arm_add_imm12(u32 rd, u32 rn, u32 rn_is_imm12) {
    173   /* ADDW rd, rn, #imm12 (raw 12-bit, no flags). rn_is_imm12 is the imm12. */
    174   u32 imm12 = rn_is_imm12 & 0xfffu;
    175   u32 i = (imm12 >> 11) & 1u, imm3 = (imm12 >> 8) & 7u, imm8 = imm12 & 0xffu;
    176   return arm_t32(0xf200u | (i << 10) | rn, (imm3 << 12) | (rd << 8) | imm8);
    177 }
    178 static inline u32 arm_sub_imm12(u32 rd, u32 rn, u32 imm12) {
    179   /* SUBW rd, rn, #imm12 (raw 12-bit, no flags). */
    180   u32 v = imm12 & 0xfffu;
    181   u32 i = (v >> 11) & 1u, imm3 = (v >> 8) & 7u, imm8 = v & 0xffu;
    182   return arm_t32(0xf2a0u | (i << 10) | rn, (imm3 << 12) | (rd << 8) | imm8);
    183 }
    184 /* CMP.W rn, #modimm (SUB S=1, Rd=1111). */
    185 static inline u32 arm_cmp_imm(u32 rn, u32 out12) { return arm_dp_imm(13u, 1u, 0xfu, rn, out12); }
    186 
    187 /* --------- shifts --------- */
    188 /* MOV.W rd, rm, <type> #sh : type LSL=0,LSR=1,ASR=2,ROR=3 (immediate). */
    189 static inline u32 arm_shift_imm(u32 type, u32 rd, u32 rm, u32 sh) {
    190   u32 imm3 = (sh >> 2) & 7u, imm2 = sh & 3u;
    191   return arm_t32(0xea4fu, (imm3 << 12) | (rd << 8) | (imm2 << 6) | (type << 4) | rm);
    192 }
    193 /* LSL/LSR/ASR (register): Rd = Rn shifted by Rm. */
    194 static inline u32 arm_shift_reg(u32 type, u32 rd, u32 rn, u32 rm) {
    195   u32 hw1 = 0xfa00u | (type << 5) | rn; /* LSL=0xFA00, LSR=0xFA20, ASR=0xFA40 */
    196   return arm_t32(hw1, 0xf000u | (rd << 8) | rm);
    197 }
    198 
    199 /* --------- multiply / divide --------- */
    200 static inline u32 arm_mul(u32 rd, u32 rn, u32 rm) {
    201   return arm_t32(0xfb00u | rn, 0xf000u | (rd << 8) | rm);
    202 }
    203 static inline u32 arm_mls(u32 rd, u32 rn, u32 rm, u32 ra) {
    204   return arm_t32(0xfb00u | rn, (ra << 12) | (rd << 8) | 0x10u | rm);
    205 }
    206 static inline u32 arm_sdiv(u32 rd, u32 rn, u32 rm) {
    207   return arm_t32(0xfb90u | rn, 0xf0f0u | (rd << 8) | rm);
    208 }
    209 static inline u32 arm_udiv(u32 rd, u32 rn, u32 rm) {
    210   return arm_t32(0xfbb0u | rn, 0xf0f0u | (rd << 8) | rm);
    211 }
    212 
    213 /* --------- sign/zero extends --------- */
    214 static inline u32 arm_sxtb(u32 rd, u32 rm) { return arm_t32(0xfa4fu, 0xf080u | (rd << 8) | rm); }
    215 static inline u32 arm_sxth(u32 rd, u32 rm) { return arm_t32(0xfa0fu, 0xf080u | (rd << 8) | rm); }
    216 static inline u32 arm_uxtb(u32 rd, u32 rm) { return arm_t32(0xfa5fu, 0xf080u | (rd << 8) | rm); }
    217 static inline u32 arm_uxth(u32 rd, u32 rm) { return arm_t32(0xfa1fu, 0xf080u | (rd << 8) | rm); }
    218 
    219 /* --------- loads / stores (immediate, positive 12-bit offset, T3) --------- */
    220 static inline u32 arm_ldr_imm(u32 rt, u32 rn, u32 imm12) {
    221   return arm_t32(0xf8d0u | rn, (rt << 12) | (imm12 & 0xfffu));
    222 }
    223 static inline u32 arm_str_imm(u32 rt, u32 rn, u32 imm12) {
    224   return arm_t32(0xf8c0u | rn, (rt << 12) | (imm12 & 0xfffu));
    225 }
    226 static inline u32 arm_ldrb_imm(u32 rt, u32 rn, u32 imm12) {
    227   return arm_t32(0xf890u | rn, (rt << 12) | (imm12 & 0xfffu));
    228 }
    229 static inline u32 arm_strb_imm(u32 rt, u32 rn, u32 imm12) {
    230   return arm_t32(0xf880u | rn, (rt << 12) | (imm12 & 0xfffu));
    231 }
    232 static inline u32 arm_ldrh_imm(u32 rt, u32 rn, u32 imm12) {
    233   return arm_t32(0xf8b0u | rn, (rt << 12) | (imm12 & 0xfffu));
    234 }
    235 static inline u32 arm_strh_imm(u32 rt, u32 rn, u32 imm12) {
    236   return arm_t32(0xf8a0u | rn, (rt << 12) | (imm12 & 0xfffu));
    237 }
    238 static inline u32 arm_ldrsb_imm(u32 rt, u32 rn, u32 imm12) {
    239   return arm_t32(0xf990u | rn, (rt << 12) | (imm12 & 0xfffu));
    240 }
    241 static inline u32 arm_ldrsh_imm(u32 rt, u32 rn, u32 imm12) {
    242   return arm_t32(0xf9b0u | rn, (rt << 12) | (imm12 & 0xfffu));
    243 }
    244 
    245 /* LDR/STR (immediate) T4: [Rn, #+/-imm8], offset addressing (P=1, W=0).
    246  * `add` = 1 for +imm8, 0 for -imm8. hw1 base picks the op + width:
    247  *   LDR=0xF850 STR=0xF840 LDRB=0xF810 STRB=0xF800 LDRH=0xF830 STRH=0xF820
    248  *   LDRSB=0xF910 LDRSH=0xF930. */
    249 static inline u32 arm_ldst_t4(u32 hw1_base, u32 rt, u32 rn, u32 imm8, u32 add) {
    250   return arm_t32(hw1_base | rn,
    251                  (rt << 12) | 0xc00u | (add << 9) | (imm8 & 0xffu));
    252 }
    253 
    254 /* 16-bit MOV (register, high-reg form T1): handles all of r0..r15 incl. sp. */
    255 static inline u16 arm_mov_hi(u32 rd, u32 rm) {
    256   return (u16)(0x4600u | ((rd >> 3) << 7) | (rm << 3) | (rd & 7u));
    257 }
    258 
    259 /* --------- exclusive (atomic) loads / stores + barriers (ARMv7-M) ---------
    260  * LDREX/STREX are word-only with a #imm8*4 offset (T1); the byte/half variants
    261  * (LDREX{B,H}/STREX{B,H}) take no offset. STREX writes a 0/1 success status to
    262  * Rd. DMB orders memory; CLREX clears the exclusive monitor. The atomic lowering
    263  * uses these for <=4-byte _Atomic ops (8-byte routes to the spinlock libcall —
    264  * M-profile has no LDREXD/STREXD). */
    265 static inline u32 arm_ldrex(u32 rt, u32 rn, u32 imm8) {
    266   return arm_t32(0xe850u | rn, (rt << 12) | 0xf00u | (imm8 & 0xffu));
    267 }
    268 static inline u32 arm_strex(u32 rd, u32 rt, u32 rn, u32 imm8) {
    269   return arm_t32(0xe840u | rn, (rt << 12) | (rd << 8) | (imm8 & 0xffu));
    270 }
    271 static inline u32 arm_ldrexb(u32 rt, u32 rn) {
    272   return arm_t32(0xe8d0u | rn, (rt << 12) | 0xf4fu);
    273 }
    274 static inline u32 arm_strexb(u32 rd, u32 rt, u32 rn) {
    275   return arm_t32(0xe8c0u | rn, (rt << 12) | 0xf40u | rd);
    276 }
    277 static inline u32 arm_ldrexh(u32 rt, u32 rn) {
    278   return arm_t32(0xe8d0u | rn, (rt << 12) | 0xf5fu);
    279 }
    280 static inline u32 arm_strexh(u32 rd, u32 rt, u32 rn) {
    281   return arm_t32(0xe8c0u | rn, (rt << 12) | 0xf50u | rd);
    282 }
    283 /* arm_dmb/arm_dsb/arm_isb are defined once in the barrier section below. */
    284 static inline u32 arm_clrex(void) { return arm_t32(0xf3bfu, 0x8f2fu); }
    285 
    286 /* --------- branches (placeholders; immediate filled by reloc/label-fixup) ---
    287  * The immediate fields use the canonical clang/gas "branch to self" pattern
    288  * (encoded displacement -4). For ARM ELF the branch relocs are REL, so the
    289  * in-field bits ARE the addend an external linker (ld.lld, gas's ld) reads; an
    290  * all-zero field decodes (via I1=NOT(J1^S)) to a bogus +0xC00000, which those
    291  * linkers then apply. kit's OWN linker overwrites the field (mask 0xf800d000 /
    292  * the JUMP19 imm mask), so the placeholder is inert for kit ld — these values
    293  * exist purely to keep kit objects linkable by stock ARM linkers. */
    294 static inline u32 arm_b_w(void) { return arm_t32(0xf7ffu, 0xbffeu); }       /* B.W (T4) */
    295 static inline u32 arm_b_cond_w(u32 cond) {                                  /* B<cond>.W (T3) */
    296   return arm_t32(0xf43fu | (cond << 6), 0xaffeu);
    297 }
    298 static inline u32 arm_bl(void) { return arm_t32(0xf7ffu, 0xfffeu); }        /* BL (T1) */
    299 
    300 /* --------- 16-bit instructions --------- */
    301 static inline u16 arm_bx(u32 rm) { return (u16)(0x4700u | (rm << 3)); }
    302 static inline u16 arm_blx_reg(u32 rm) { return (u16)(0x4780u | (rm << 3)); }
    303 static inline u16 arm_bkpt(u32 imm8) { return (u16)(0xbe00u | (imm8 & 0xffu)); }
    304 /* UDF #imm8 (T1, permanently undefined) — the coroutine trampoline's trap on a
    305  * returned entry fn. Same 16-bit imm8 shape as BKPT, different opcode. */
    306 static inline u16 arm_udf(u32 imm8) { return (u16)(0xde00u | (imm8 & 0xffu)); }
    307 static inline u16 arm_nop16(void) { return (u16)0xbf00u; }
    308 /* NOP.W (32-bit) — pads a multi-slot prologue placeholder region. */
    309 static inline u32 arm_nop32(void) { return arm_t32(0xf3afu, 0x8000u); }
    310 
    311 /* --------- push / pop (32-bit, STMDB sp! / LDMIA sp!) --------- */
    312 static inline u32 arm_push_w(u32 reglist) { return arm_t32(0xe92du, reglist & 0xdfffu); }
    313 static inline u32 arm_pop_w(u32 reglist) { return arm_t32(0xe8bdu, reglist & 0xffffu); }
    314 
    315 /* --------- 16-bit narrow data-processing / move-immediate (Thumb-1) ---------
    316  * EVERY 16-bit data-processing form below ALWAYS sets the condition flags (there
    317  * is no S=0 16-bit DP encoding outside an IT block). The native backend may only
    318  * emit these at FLAG-DEAD sites (kit keeps flags live solely across CMP→IT, and
    319  * comparisons re-emit their CMP, so binop/move/shift results never carry a live
    320  * flag dependency). Never narrow a MOV.W that sits between a CMP and its
    321  * IT-predicated consumer. Low regs r0..r7 only (3-bit rd/rn/rm fields). */
    322 static inline u16 arm_movs_imm8(u32 rd, u32 imm8) {
    323   return (u16)(0x2000u | ((rd & 7u) << 8) | (imm8 & 0xffu));
    324 }
    325 static inline u16 arm_adds_imm8(u32 rdn, u32 imm8) {
    326   return (u16)(0x3000u | ((rdn & 7u) << 8) | (imm8 & 0xffu));
    327 }
    328 static inline u16 arm_subs_imm8(u32 rdn, u32 imm8) {
    329   return (u16)(0x3800u | ((rdn & 7u) << 8) | (imm8 & 0xffu));
    330 }
    331 static inline u16 arm_adds_imm3(u32 rd, u32 rn, u32 imm3) {
    332   return (u16)(0x1c00u | ((imm3 & 7u) << 6) | ((rn & 7u) << 3) | (rd & 7u));
    333 }
    334 static inline u16 arm_subs_imm3(u32 rd, u32 rn, u32 imm3) {
    335   return (u16)(0x1e00u | ((imm3 & 7u) << 6) | ((rn & 7u) << 3) | (rd & 7u));
    336 }
    337 static inline u16 arm_adds_reg16(u32 rd, u32 rn, u32 rm) {
    338   return (u16)(0x1800u | ((rm & 7u) << 6) | ((rn & 7u) << 3) | (rd & 7u));
    339 }
    340 static inline u16 arm_subs_reg16(u32 rd, u32 rn, u32 rm) {
    341   return (u16)(0x1a00u | ((rm & 7u) << 6) | ((rn & 7u) << 3) | (rd & 7u));
    342 }
    343 /* LSLS/LSRS/ASRS rd, rm, #imm5 (type LSL=0x0000, LSR=0x0800, ASR=0x1000). */
    344 static inline u16 arm_shift_imm16(u32 base, u32 rd, u32 rm, u32 imm5) {
    345   return (u16)(base | ((imm5 & 0x1fu) << 6) | ((rm & 7u) << 3) | (rd & 7u));
    346 }
    347 static inline u16 arm_lsls_imm16(u32 rd, u32 rm, u32 imm5) { return arm_shift_imm16(0x0000u, rd, rm, imm5); }
    348 static inline u16 arm_lsrs_imm16(u32 rd, u32 rm, u32 imm5) { return arm_shift_imm16(0x0800u, rd, rm, imm5); }
    349 static inline u16 arm_asrs_imm16(u32 rd, u32 rm, u32 imm5) { return arm_shift_imm16(0x1000u, rd, rm, imm5); }
    350 
    351 /* --------- 16-bit loads / stores (Thumb-1) --------- no flags ---------
    352  * T1 [rn, #imm5*scale]: word ×4 (LDR 0x6800 / STR 0x6000), byte ×1 (LDRB 0x7800
    353  * / STRB 0x7000), half ×2 (LDRH 0x8800 / STRH 0x8000). rt/rn are low regs.
    354  * T2 [sp, #imm8*4]: LDR 0x9800 / STR 0x9000, rt in [10:8]. */
    355 static inline u16 arm_ldst_i5_16(u32 base, u32 rt, u32 rn, u32 imm5) {
    356   return (u16)(base | ((imm5 & 0x1fu) << 6) | ((rn & 7u) << 3) | (rt & 7u));
    357 }
    358 static inline u16 arm_ldst_sp_16(u32 base, u32 rt, u32 imm8) {
    359   return (u16)(base | ((rt & 7u) << 8) | (imm8 & 0xffu));
    360 }
    361 /* ADD rd, sp, #imm8*4 (T1). */
    362 static inline u16 arm_add_sp_imm16(u32 rd, u32 imm8) {
    363   return (u16)(0xa800u | ((rd & 7u) << 8) | (imm8 & 0xffu));
    364 }
    365 /* ADD/SUB sp, sp, #imm7*4 (T2). No flags (special SP-adjust encoding). */
    366 static inline u16 arm_add_sp_sp_imm16(u32 imm7) { return (u16)(0xb000u | (imm7 & 0x7fu)); }
    367 static inline u16 arm_sub_sp_sp_imm16(u32 imm7) { return (u16)(0xb080u | (imm7 & 0x7fu)); }
    368 
    369 /* 16-bit PUSH {list8, lr} / POP {list8, pc}. `list8` covers r0..r7; the high
    370  * bit pulls in LR (push) or PC (pop). No flags. The common {r7,lr}/{r7,pc}
    371  * frame pair fits exactly. */
    372 static inline u16 arm_push16(u32 list8, u32 with_lr) {
    373   return (u16)(0xb400u | ((with_lr & 1u) << 8) | (list8 & 0xffu));
    374 }
    375 static inline u16 arm_pop16(u32 list8, u32 with_pc) {
    376   return (u16)(0xbc00u | ((with_pc & 1u) << 8) | (list8 & 0xffu));
    377 }
    378 
    379 /* --------- bit-field extract / insert (Thumb-2 T1) --------- */
    380 /* lsb is split into imm3:imm2 = (lsb>>2):(lsb&3). UBFX/SBFX encode width-1;
    381  * BFI/BFC encode msb = lsb + width - 1. */
    382 static inline u32 arm_ubfx(u32 rd, u32 rn, u32 lsb, u32 width) {
    383   u32 imm3 = (lsb >> 2) & 7u, imm2 = lsb & 3u, widthm1 = (width - 1u) & 0x1fu;
    384   return arm_t32(0xf3c0u | (rn & 0xfu),
    385                  (imm3 << 12) | (rd << 8) | (imm2 << 6) | widthm1);
    386 }
    387 static inline u32 arm_sbfx(u32 rd, u32 rn, u32 lsb, u32 width) {
    388   u32 imm3 = (lsb >> 2) & 7u, imm2 = lsb & 3u, widthm1 = (width - 1u) & 0x1fu;
    389   return arm_t32(0xf340u | (rn & 0xfu),
    390                  (imm3 << 12) | (rd << 8) | (imm2 << 6) | widthm1);
    391 }
    392 /* BFI rd, rn, #lsb, #width : insert width bits of rn into rd starting at lsb. */
    393 static inline u32 arm_bfi(u32 rd, u32 rn, u32 lsb, u32 width) {
    394   u32 imm3 = (lsb >> 2) & 7u, imm2 = lsb & 3u,
    395       msb = (lsb + width - 1u) & 0x1fu;
    396   return arm_t32(0xf360u | (rn & 0xfu), (imm3 << 12) | (rd << 8) | (imm2 << 6) | msb);
    397 }
    398 /* BFC rd, #lsb, #width : clear width bits of rd (BFI with Rn=1111). */
    399 static inline u32 arm_bfc(u32 rd, u32 lsb, u32 width) {
    400   return arm_bfi(rd, 0xfu, lsb, width);
    401 }
    402 
    403 /* --------- count / reverse --------- */
    404 static inline u32 arm_clz(u32 rd, u32 rm) {
    405   return arm_t32(0xfab0u | (rm & 0xfu), 0xf080u | (rd << 8) | (rm & 0xfu));
    406 }
    407 static inline u32 arm_rbit(u32 rd, u32 rm) {
    408   return arm_t32(0xfa90u | (rm & 0xfu), 0xf0a0u | (rd << 8) | (rm & 0xfu));
    409 }
    410 /* 16-bit byte-reverse (T1): REV / REV16 / REVSH. r0..r7 ONLY (3-bit fields) —
    411  * a caller with a high register (e.g. the IP/r12 codegen scratch) must use the
    412  * 32-bit .W forms below, else the register silently truncates mod 8. */
    413 static inline u16 arm_rev(u32 rd, u32 rm) { return (u16)(0xba00u | ((rm & 7u) << 3) | (rd & 7u)); }
    414 static inline u16 arm_rev16(u32 rd, u32 rm) { return (u16)(0xba40u | ((rm & 7u) << 3) | (rd & 7u)); }
    415 static inline u16 arm_revsh(u32 rd, u32 rm) { return (u16)(0xbac0u | ((rm & 7u) << 3) | (rd & 7u)); }
    416 /* 32-bit byte-reverse (T2): REV.W / REV16.W — any register r0..r14. */
    417 static inline u32 arm_rev_w(u32 rd, u32 rm) {
    418   return arm_t32(0xfa90u | (rm & 0xfu), 0xf080u | ((rd & 0xfu) << 8) | (rm & 0xfu));
    419 }
    420 static inline u32 arm_rev16_w(u32 rd, u32 rm) {
    421   return arm_t32(0xfa90u | (rm & 0xfu), 0xf090u | ((rd & 0xfu) << 8) | (rm & 0xfu));
    422 }
    423 
    424 /* --------- barriers (32-bit T1, hw2 = 0x8f00 | op<<4 | option) --------- */
    425 /* option = 0xf for the "sy" full-system barrier (the only form kit emits). */
    426 /* --------- multiply long + MLA (T1) --------- */
    427 static inline u32 arm_mla(u32 rd, u32 rn, u32 rm, u32 ra) {
    428   return arm_t32(0xfb00u | rn, (ra << 12) | (rd << 8) | rm);
    429 }
    430 static inline u32 arm_umull(u32 rdlo, u32 rdhi, u32 rn, u32 rm) {
    431   return arm_t32(0xfba0u | rn, (rdlo << 12) | (rdhi << 8) | rm);
    432 }
    433 static inline u32 arm_smull(u32 rdlo, u32 rdhi, u32 rn, u32 rm) {
    434   return arm_t32(0xfb80u | rn, (rdlo << 12) | (rdhi << 8) | rm);
    435 }
    436 
    437 /* --------- ARMv7E-M DSP saturating (T1) ---------
    438  * SSAT/USAT saturate Rm to a signed/unsigned `sat`-bit value (no-shift form):
    439  *   SSAT Rd, #sat, Rm : hw1 = 0xF300|Rn(=Rm), hw2 = (Rd<<8) | ((sat-1)&0x1f)
    440  *   USAT Rd, #sat, Rm : hw1 = 0xF380|Rm,        hw2 = (Rd<<8) | (sat&0x1f)
    441  * (SSAT encodes sat_imm = sat-1; USAT encodes sat directly. The LSL/ASR shift
    442  * operand is omitted in this cut — see isa.c for the decode-mask rationale.)
    443  *
    444  * QADD/QSUB/QDADD/QDSUB are register saturating add/sub (ARM operand order
    445  * Rd, Rm, Rn): hw1 = 0xFA80|Rn, hw2 = 0xF000 | (Rd<<8) | (op<<4) | Rm, with
    446  * op QADD=0x8, QDADD=0x9, QSUB=0xA, QDSUB=0xB. Note Rn lands in hw1[3:0] and
    447  * Rm in hw2[3:0]. */
    448 static inline u32 arm_ssat(u32 rd, u32 sat, u32 rm) {
    449   return arm_t32(0xf300u | (rm & 0xfu), (rd << 8) | ((sat - 1u) & 0x1fu));
    450 }
    451 static inline u32 arm_usat(u32 rd, u32 sat, u32 rm) {
    452   return arm_t32(0xf380u | (rm & 0xfu), (rd << 8) | (sat & 0x1fu));
    453 }
    454 static inline u32 arm_qop(u32 op, u32 rd, u32 rm, u32 rn) {
    455   return arm_t32(0xfa80u | (rn & 0xfu),
    456                  0xf000u | (rd << 8) | ((op & 0xfu) << 4) | (rm & 0xfu));
    457 }
    458 static inline u32 arm_qadd(u32 rd, u32 rm, u32 rn) { return arm_qop(0x8u, rd, rm, rn); }
    459 static inline u32 arm_qdadd(u32 rd, u32 rm, u32 rn) { return arm_qop(0x9u, rd, rm, rn); }
    460 static inline u32 arm_qsub(u32 rd, u32 rm, u32 rn) { return arm_qop(0xau, rd, rm, rn); }
    461 static inline u32 arm_qdsub(u32 rd, u32 rm, u32 rn) { return arm_qop(0xbu, rd, rm, rn); }
    462 
    463 static inline u32 arm_dmb(u32 option) { return arm_t32(0xf3bfu, 0x8f50u | (option & 0xfu)); }
    464 static inline u32 arm_dsb(u32 option) { return arm_t32(0xf3bfu, 0x8f40u | (option & 0xfu)); }
    465 static inline u32 arm_isb(u32 option) { return arm_t32(0xf3bfu, 0x8f60u | (option & 0xfu)); }
    466 
    467 /* --------- 16-bit hint instructions (0xbf__) --------- */
    468 static inline u16 arm_yield16(void) { return (u16)0xbf10u; }
    469 static inline u16 arm_wfe16(void) { return (u16)0xbf20u; }
    470 static inline u16 arm_wfi16(void) { return (u16)0xbf30u; }
    471 static inline u16 arm_sev16(void) { return (u16)0xbf40u; }
    472 /* --------- table branch TBB/TBH (T1) --------- */
    473 /* TBB [rn, rm] : hw1=0xE8D0|rn, hw2=0xF000|rm. TBH adds H=1 (<<4). */
    474 static inline u32 arm_tbb(u32 rn, u32 rm) { return arm_t32(0xe8d0u | rn, 0xf000u | rm); }
    475 static inline u32 arm_tbh(u32 rn, u32 rm) { return arm_t32(0xe8d0u | rn, 0xf010u | rm); }
    476 
    477 /* --------- 16-bit compare-and-branch CBZ/CBNZ (T1) --------- */
    478 /* Encodes a forward branch of `imm6` half-words (PC-rel, +4..+130). The split
    479  * immediate is i(bit9):imm5(bits7:3). op=1 selects CBNZ. */
    480 static inline u16 arm_cbz_raw(u32 op, u32 rn, u32 imm6) {
    481   u32 i = (imm6 >> 5) & 1u, imm5 = imm6 & 0x1fu;
    482   return (u16)(0xb100u | (op << 11) | (i << 9) | (imm5 << 3) | (rn & 7u));
    483 }
    484 static inline u16 arm_cbz(u32 rn, u32 imm6) { return arm_cbz_raw(0u, rn, imm6); }
    485 static inline u16 arm_cbnz(u32 rn, u32 imm6) { return arm_cbz_raw(1u, rn, imm6); }
    486 
    487 /* --------- IT (16-bit) --------- */
    488 /* IT{x{y{z}}} cc : firstcond in [7:4], mask in [3:0]. A single-insn IT block
    489  * uses mask=0b1000 (0x8). Multi-insn masks encode the T/E pattern. */
    490 static inline u16 arm_it(u32 firstcond, u32 mask) {
    491   return (u16)(0xbf00u | ((firstcond & 0xfu) << 4) | (mask & 0xfu));
    492 }
    493 
    494 /* =====================================================================
    495  * Descriptor table + disassembler/assembler lookup (defined in isa.c).
    496  *
    497  * Each row records (mnemonic, match, mask, fmt, flags, width). A 16-bit row
    498  * sets ARM_FMT_W16 in flags and stores match/mask in the low 16 bits; a 32-bit
    499  * row matches the full (hw1<<16)|hw2 word. arm32_disasm_find linear-scans and
    500  * returns the first row whose masked bits match (first-match wins; aliases sit
    501  * before the canonical row). arm32_asm_find maps a mnemonic to its row.
    502  * ===================================================================== */
    503 
    504 typedef enum Arm32Format {
    505   ARM_FMT_NONE = 0,    /* no operands (NOP16) */
    506   ARM_FMT_DP_REG,      /* dp (reg): rd, rn, rm  [op4 family, S flag] */
    507   ARM_FMT_DP_IMM,      /* dp (modified-imm): rd, rn, #imm */
    508   ARM_FMT_MOV_IMM,     /* MOV.W/MVN.W rd, #modimm (rn=1111, no rn print) */
    509   ARM_FMT_CMP_REG,     /* CMP.W/CMN.W/TST/TEQ rn, rm (rd=1111) */
    510   ARM_FMT_CMP_IMM,     /* CMP.W/CMN.W/TST/TEQ rn, #modimm (rd=1111) */
    511   ARM_FMT_MOV_REG,     /* MOV.W/MVN.W rd, rm (ORR/ORN rn=1111) */
    512   ARM_FMT_MOVW,        /* MOVW/MOVT rd, #imm16 */
    513   ARM_FMT_ADDW,        /* ADDW/SUBW rd, rn, #imm12 */
    514   ARM_FMT_SHIFT_IMM,   /* LSL/LSR/ASR/ROR rd, rm, #sh */
    515   ARM_FMT_SHIFT_REG,   /* LSL/LSR/ASR/ROR rd, rn, rm */
    516   ARM_FMT_MUL,         /* MUL rd, rn, rm */
    517   ARM_FMT_MLA,         /* MLA/MLS rd, rn, rm, ra */
    518   ARM_FMT_DIV,         /* SDIV/UDIV rd, rn, rm */
    519   ARM_FMT_MULL,        /* UMULL/SMULL rdlo, rdhi, rn, rm */
    520   ARM_FMT_EXT,         /* SXTB/SXTH/UXTB/UXTH rd, rm */
    521   ARM_FMT_REV,         /* REV/REV16/REVSH/RBIT/CLZ rd, rm */
    522   ARM_FMT_BFX,         /* SBFX/UBFX rd, rn, #lsb, #width */
    523   ARM_FMT_BFI,         /* BFI rd, rn, #lsb, #width */
    524   ARM_FMT_BFC,         /* BFC rd, #lsb, #width */
    525   ARM_FMT_SAT,         /* SSAT/USAT rd, #sat, rm */
    526   ARM_FMT_QADD,        /* QADD/QSUB/QDADD/QDSUB rd, rm, rn */
    527   ARM_FMT_LDST_T3,     /* LDR/STR{B,H,SB,SH} rt, [rn, #imm12] */
    528   ARM_FMT_LDST_T4,     /* LDR/STR{B,H,SB,SH} rt, [rn, #+/-imm8] */
    529   ARM_FMT_LDREX,       /* LDREX rt, [rn{, #imm}] */
    530   ARM_FMT_STREX,       /* STREX rd, rt, [rn{, #imm}] */
    531   ARM_FMT_PUSHPOP,     /* PUSH.W/POP.W {reglist} */
    532   ARM_FMT_BARRIER,     /* DMB/DSB/ISB {option} */
    533   ARM_FMT_TB,          /* TBB/TBH [rn, rm] */
    534   ARM_FMT_BRANCH_T4,   /* B.W <label> (24-bit) */
    535   ARM_FMT_BRANCH_T3,   /* B<cond>.W <label> (20-bit) */
    536   ARM_FMT_BL,          /* BL <label> */
    537   ARM_FMT_B16,         /* B <label> (16-bit T2, 11-bit) */
    538   ARM_FMT_BCC16,       /* B<cond> <label> (16-bit T1, 8-bit) */
    539   ARM_FMT_CBZ,         /* CBZ/CBNZ rn, <label> */
    540   ARM_FMT_IT,          /* IT{xyz} cc */
    541   ARM_FMT_MOVHI16,     /* MOV (reg, high) rd, rm (16-bit) */
    542   ARM_FMT_BX,          /* BX/BLX rm (16-bit) */
    543   ARM_FMT_BKPT,        /* BKPT #imm8 (16-bit) */
    544   ARM_FMT_EXT16,       /* 16-bit SXTB/SXTH/UXTB/UXTH/REV/REV16/REVSH rd, rm */
    545   /* ---- additional 16-bit (Thumb-1) families ---- */
    546   ARM_FMT_DPI8_16,     /* MOVS/CMP/ADDS/SUBS rd|rn, #imm8 (rd/rn in [10:8]) */
    547   ARM_FMT_ADDSUB3_16,  /* ADDS/SUBS rd, rn, #imm3 (rd[2:0], rn[5:3], imm[8:6]) */
    548   ARM_FMT_ADDSUBR_16,  /* ADDS/SUBS rd, rn, rm (rd[2:0], rn[5:3], rm[8:6]) */
    549   ARM_FMT_SHIFTI_16,   /* LSLS/LSRS/ASRS rd, rm, #imm5 */
    550   ARM_FMT_ALU_16,      /* 16-bit data-proc reg: rdn, rm (op in [9:6]) */
    551   ARM_FMT_HIREG_16,    /* ADD/CMP (hi reg) rdn, rm */
    552   ARM_FMT_LDSTI5_16,   /* LDR/STR{,B,H} rt, [rn, #imm5*scale] */
    553   ARM_FMT_LDSTSP_16,   /* LDR/STR rt, [sp, #imm8*4] (rt in [10:8]) */
    554   ARM_FMT_ADDSP_16,    /* ADD rd, sp/pc, #imm8*4 (rd in [10:8]) */
    555   ARM_FMT_ADJSP_16,    /* ADD/SUB sp, sp, #imm7*4 */
    556   ARM_FMT_PUSHPOP_16,  /* PUSH/POP {reglist} (16-bit) */
    557 } Arm32Format;
    558 
    559 /* Stable decoded-opcode ids (KitDecodedInsn.opcode). Kept small: only the ops
    560  * an emulator/decoder consumer needs to special-case carry a distinct id. */
    561 typedef enum Arm32DecodedOpcode {
    562   ARM32_DEC_UNKNOWN = 0,
    563   ARM32_DEC_BL,
    564   ARM32_DEC_BLX,
    565   ARM32_DEC_BX,
    566   ARM32_DEC_B,
    567   ARM32_DEC_BCOND,
    568   ARM32_DEC_BKPT,
    569   ARM32_DEC_IT,
    570 } Arm32DecodedOpcode;
    571 
    572 /* ---- flags column on Arm32InsnDesc ---- */
    573 #define ARM_FMT_W16 0x01u   /* 16-bit instruction (match/mask in low 16 bits) */
    574 #define ARM_ASMFL_ALIAS 0x02u /* alias row: preferred disasm spelling */
    575 
    576 typedef struct Arm32InsnDesc {
    577   Slice mnemonic;
    578   u32 match;
    579   u32 mask;
    580   u8 fmt;   /* Arm32Format */
    581   u8 flags; /* ARM_FMT_W16 / ARM_ASMFL_ALIAS */
    582   u8 pad[2];
    583 } Arm32InsnDesc;
    584 
    585 extern const Arm32InsnDesc arm32_insn_table[];
    586 extern const u32 arm32_insn_table_n;
    587 
    588 /* Disassembler lookup: first masked-match row, or NULL. `is16` picks the
    589  * 16-bit vs 32-bit subset (the caller has already determined width). */
    590 const Arm32InsnDesc* arm32_disasm_find(u32 word, int is16);
    591 /* Assembler lookup: mnemonic -> descriptor (non-alias preferred), or NULL. */
    592 const Arm32InsnDesc* arm32_asm_find(Slice mnemonic);
    593 
    594 /* Condition-code name for a 4-bit cond field (0..14), or "" for AL/invalid. */
    595 const char* arm32_cond_name(u32 cond);
    596 /* Condition-code value for a 2-char suffix, or -1. */
    597 int arm32_cond_from_name(Slice s);
    598 /* Condition field of a B<cond>.W (T3) word (disasm appends it to the mnemonic). */
    599 u32 arm32_branch_t3_cond(u32 w);
    600 
    601 /* Shifted-MOV (ORR/ORN rn=1111, ARM_FMT_MOV_REG) shift decode. Returns the
    602  * shift mnemonic the MOV form aliases to ("lsl.w"/"lsr.w"/"asr.w"/"ror.w", or
    603  * "rrx" for ROR #0), or NULL when there is no shift (a plain `mov.w`). Decodes
    604  * imm3:imm2 (hw2[14:12]/[7:6]) and type (hw2[5:4]); applies the LSR/ASR amount
    605  * 0 -> 32 and ROR amount 0 -> rrx rules. *type_out gets the raw 2-bit shift
    606  * type, *amount_out the rendered amount (undefined/unused for rrx). Both out
    607  * pointers may be NULL. The MVN form is NOT aliased; callers reuse type_out/
    608  * amount_out to append a `, <sh> #n` suffix. */
    609 const char* arm32_mov_shift_alias(u32 word, u32* type_out, u32* amount_out);
    610 
    611 /* Render operand text for `word` into `sb` using `desc->fmt`. `vaddr` is the
    612  * instruction address (for PC-relative branch targets); 0 if unknown. */
    613 void arm32_print_operands(StrBuf* sb, const Arm32InsnDesc* desc, u32 word,
    614                           u64 vaddr);
    615 
    616 #endif