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