Skip to main content

synth_backend/
arm_encoder.rs

1//! ARM Code Encoder - Converts ARM instructions to binary machine code
2//!
3//! Generates ARM32/Thumb-2 machine code from ARM instruction structures
4
5use synth_core::Result;
6use synth_core::target::FPUPrecision;
7use synth_synthesis::contracts::encoding as encoding_contracts;
8use synth_synthesis::{ArmOp, MemAddr, MveSize, Operand2, QReg, Reg, VfpReg};
9
10/// ARM instruction encoding
11pub struct ArmEncoder {
12    /// Use Thumb mode (vs ARM mode)
13    thumb_mode: bool,
14    /// FPU capability for VFP instruction encoding
15    #[allow(dead_code)]
16    fpu: Option<FPUPrecision>,
17}
18
19impl ArmEncoder {
20    /// Create a new ARM encoder in ARM32 mode
21    pub fn new_arm32() -> Self {
22        Self {
23            thumb_mode: false,
24            fpu: None,
25        }
26    }
27
28    /// Create a new ARM encoder in Thumb-2 mode
29    pub fn new_thumb2() -> Self {
30        Self {
31            thumb_mode: true,
32            fpu: None,
33        }
34    }
35
36    /// Create a new Thumb-2 encoder with FPU capability
37    pub fn new_thumb2_with_fpu(fpu: Option<FPUPrecision>) -> Self {
38        Self {
39            thumb_mode: true,
40            fpu,
41        }
42    }
43
44    /// Encode a single ARM instruction to bytes
45    pub fn encode(&self, op: &ArmOp) -> Result<Vec<u8>> {
46        if self.thumb_mode {
47            self.encode_thumb(op)
48        } else {
49            self.encode_arm(op)
50        }
51    }
52
53    /// Encode an ARM instruction in ARM32 mode (32-bit instructions)
54    /// #206: encode an ARM32 (A32) load/store whose address uses a register
55    /// offset (`[rn, rm{, #off}]`). Returns `None` for ops with no register
56    /// offset (the caller falls through to the immediate-form arms). Computes
57    /// `ip = base + rm` then re-encodes the op against `[ip, #off]`, which works
58    /// uniformly for word/byte/halfword/signed forms. IP (R12) is the scratch
59    /// register the selector already treats as clobberable across memory ops.
60    fn encode_arm_reg_offset_mem(&self, op: &ArmOp) -> Result<Option<Vec<u8>>> {
61        use synth_synthesis::Reg;
62        let addr = match op {
63            ArmOp::Ldr { addr, .. }
64            | ArmOp::Str { addr, .. }
65            | ArmOp::Ldrb { addr, .. }
66            | ArmOp::Strb { addr, .. }
67            | ArmOp::Ldrh { addr, .. }
68            | ArmOp::Strh { addr, .. }
69            | ArmOp::Ldrsb { addr, .. }
70            | ArmOp::Ldrsh { addr, .. } => addr,
71            _ => return Ok(None),
72        };
73        let Some(rm) = addr.offset_reg else {
74            return Ok(None);
75        };
76        let ip = Reg::R12;
77        // ADD ip, base, rm  (cond=AL, opcode=ADD, S=0, register operand2)
78        let add: u32 = 0xE0800000
79            | (reg_to_bits(&addr.base) << 16)
80            | (reg_to_bits(&ip) << 12)
81            | reg_to_bits(&rm);
82        let mut bytes = add.to_le_bytes().to_vec();
83        // Re-encode the op against [ip, #off] (immediate form → no offset_reg,
84        // so this recursion hits the immediate arms, not this helper again).
85        let imm_addr = MemAddr::imm(ip, addr.offset);
86        let imm_op = match op {
87            ArmOp::Ldr { rd, .. } => ArmOp::Ldr {
88                rd: *rd,
89                addr: imm_addr,
90            },
91            ArmOp::Str { rd, .. } => ArmOp::Str {
92                rd: *rd,
93                addr: imm_addr,
94            },
95            ArmOp::Ldrb { rd, .. } => ArmOp::Ldrb {
96                rd: *rd,
97                addr: imm_addr,
98            },
99            ArmOp::Strb { rd, .. } => ArmOp::Strb {
100                rd: *rd,
101                addr: imm_addr,
102            },
103            ArmOp::Ldrh { rd, .. } => ArmOp::Ldrh {
104                rd: *rd,
105                addr: imm_addr,
106            },
107            ArmOp::Strh { rd, .. } => ArmOp::Strh {
108                rd: *rd,
109                addr: imm_addr,
110            },
111            ArmOp::Ldrsb { rd, .. } => ArmOp::Ldrsb {
112                rd: *rd,
113                addr: imm_addr,
114            },
115            ArmOp::Ldrsh { rd, .. } => ArmOp::Ldrsh {
116                rd: *rd,
117                addr: imm_addr,
118            },
119            _ => unreachable!(),
120        };
121        bytes.extend(self.encode_arm(&imm_op)?);
122        Ok(Some(bytes))
123    }
124
125    fn encode_arm(&self, op: &ArmOp) -> Result<Vec<u8>> {
126        // #206: ARM32 register-offset loads/stores. `encode_mem_addr` only
127        // returns the 12-bit immediate, so the immediate-form arms below
128        // silently DROP `addr.offset_reg` — a runtime address index vanished,
129        // turning `ldr rd,[rn,rm,#off]` into `ldr rd,[rn,#off]` (the access went
130        // to the wrong address). Compute the effective base into IP and re-encode
131        // against `[ip, #off]`, which is uniform for word/byte/halfword/signed.
132        if let Some(bytes) = self.encode_arm_reg_offset_mem(op)? {
133            return Ok(bytes);
134        }
135        let instr: u32 = match op {
136            // Data processing instructions
137            ArmOp::Add { rd, rn, op2 } => {
138                let rd_bits = reg_to_bits(rd);
139                let rn_bits = reg_to_bits(rn);
140                let (op2_bits, i_flag) = encode_operand2(op2);
141
142                // ADD encoding: cond(4) | 00 | I(1) | 0100 | S(1) | Rn(4) | Rd(4) | operand2(12)
143                0xE0800000 // condition=always(E), opcode=ADD(0100), S=0
144                    | (i_flag << 25)
145                    | (rn_bits << 16)
146                    | (rd_bits << 12)
147                    | op2_bits
148            }
149
150            ArmOp::Sub { rd, rn, op2 } => {
151                let rd_bits = reg_to_bits(rd);
152                let rn_bits = reg_to_bits(rn);
153                let (op2_bits, i_flag) = encode_operand2(op2);
154
155                // SUB encoding: opcode=0010
156                0xE0400000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
157            }
158
159            // i64 support: ADDS, ADC, SUBS, SBC for ARM32
160            ArmOp::Adds { rd, rn, op2 } => {
161                let rd_bits = reg_to_bits(rd);
162                let rn_bits = reg_to_bits(rn);
163                let (op2_bits, i_flag) = encode_operand2(op2);
164
165                // ADDS encoding: opcode=0100, S=1
166                0xE0900000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
167            }
168
169            ArmOp::Adc { rd, rn, op2 } => {
170                let rd_bits = reg_to_bits(rd);
171                let rn_bits = reg_to_bits(rn);
172                let (op2_bits, i_flag) = encode_operand2(op2);
173
174                // ADC encoding: opcode=0101
175                0xE0A00000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
176            }
177
178            ArmOp::Subs { rd, rn, op2 } => {
179                let rd_bits = reg_to_bits(rd);
180                let rn_bits = reg_to_bits(rn);
181                let (op2_bits, i_flag) = encode_operand2(op2);
182
183                // SUBS encoding: opcode=0010, S=1
184                0xE0500000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
185            }
186
187            ArmOp::Sbc { rd, rn, op2 } => {
188                let rd_bits = reg_to_bits(rd);
189                let rn_bits = reg_to_bits(rn);
190                let (op2_bits, i_flag) = encode_operand2(op2);
191
192                // SBC encoding: opcode=0110
193                0xE0C00000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
194            }
195
196            ArmOp::Mul { rd, rn, rm } => {
197                let rd_bits = reg_to_bits(rd);
198                let rn_bits = reg_to_bits(rn);
199                let rm_bits = reg_to_bits(rm);
200
201                // MUL encoding: cond(4) | 000000 | A(1) | S(1) | Rd(4) | Rn(4) | Rs(4) | 1001 | Rm(4)
202                0xE0000090 | (rd_bits << 16) | (rn_bits << 8) | rm_bits
203            }
204
205            ArmOp::Umull { rdlo, rdhi, rn, rm } => {
206                let rdlo_bits = reg_to_bits(rdlo);
207                let rdhi_bits = reg_to_bits(rdhi);
208                let rn_bits = reg_to_bits(rn);
209                let rm_bits = reg_to_bits(rm);
210
211                // UMULL encoding: cond(4) | 0000 1000 | RdHi(4) | RdLo(4) | Rm(4) | 1001 | Rn(4)
212                0xE0800090 | (rdhi_bits << 16) | (rdlo_bits << 12) | (rm_bits << 8) | rn_bits
213            }
214
215            ArmOp::Sdiv { rd, rn, rm } => {
216                let rd_bits = reg_to_bits(rd);
217                let rn_bits = reg_to_bits(rn);
218                let rm_bits = reg_to_bits(rm);
219
220                // SDIV encoding: cond(4) | 01110001 | Rd(4) | 1111 | Rm(4) | 0001 | Rn(4)
221                // ARMv7-M and above
222                0xE710F010 | (rd_bits << 16) | (rm_bits << 8) | rn_bits
223            }
224
225            ArmOp::Udiv { rd, rn, rm } => {
226                let rd_bits = reg_to_bits(rd);
227                let rn_bits = reg_to_bits(rn);
228                let rm_bits = reg_to_bits(rm);
229
230                // UDIV encoding: cond(4) | 01110011 | Rd(4) | 1111 | Rm(4) | 0001 | Rn(4)
231                // ARMv7-M and above
232                0xE730F010 | (rd_bits << 16) | (rm_bits << 8) | rn_bits
233            }
234
235            ArmOp::Mls { rd, rn, rm, ra } => {
236                let rd_bits = reg_to_bits(rd);
237                let rn_bits = reg_to_bits(rn);
238                let rm_bits = reg_to_bits(rm);
239                let ra_bits = reg_to_bits(ra);
240
241                // MLS encoding: cond(4) | 00000110 | Rd(4) | Ra(4) | Rm(4) | 1001 | Rn(4)
242                // Rd = Ra - (Rn * Rm)
243                0xE0600090 | (rd_bits << 16) | (ra_bits << 12) | (rm_bits << 8) | rn_bits
244            }
245
246            ArmOp::Mla { rd, rn, rm, ra } => {
247                let rd_bits = reg_to_bits(rd);
248                let rn_bits = reg_to_bits(rn);
249                let rm_bits = reg_to_bits(rm);
250                let ra_bits = reg_to_bits(ra);
251
252                // MLA encoding: cond(4) | 0000001 S | Rd(4) | Ra(4) | Rm(4) | 1001 | Rn(4)
253                // Rd = Ra + (Rn * Rm). Base 0xE0200090 (S=0).
254                0xE0200090 | (rd_bits << 16) | (ra_bits << 12) | (rm_bits << 8) | rn_bits
255            }
256
257            ArmOp::And { rd, rn, op2 } => {
258                let rd_bits = reg_to_bits(rd);
259                let rn_bits = reg_to_bits(rn);
260                let (op2_bits, i_flag) = encode_operand2(op2);
261
262                // AND encoding: opcode=0000
263                0xE0000000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
264            }
265
266            ArmOp::Orr { rd, rn, op2 } => {
267                let rd_bits = reg_to_bits(rd);
268                let rn_bits = reg_to_bits(rn);
269                let (op2_bits, i_flag) = encode_operand2(op2);
270
271                // ORR encoding: opcode=1100
272                0xE1800000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
273            }
274
275            ArmOp::Eor { rd, rn, op2 } => {
276                let rd_bits = reg_to_bits(rd);
277                let rn_bits = reg_to_bits(rn);
278                let (op2_bits, i_flag) = encode_operand2(op2);
279
280                // EOR encoding: opcode=0001
281                0xE0200000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
282            }
283
284            // Shift instructions
285            ArmOp::Lsl { rd, rn, shift } => {
286                let rd_bits = reg_to_bits(rd);
287                let rn_bits = reg_to_bits(rn);
288                let shift_bits = *shift & 0x1F;
289
290                // LSL encoding: MOV with shift
291                0xE1A00000 | (rd_bits << 12) | (shift_bits << 7) | rn_bits
292            }
293
294            ArmOp::Lsr { rd, rn, shift } => {
295                let rd_bits = reg_to_bits(rd);
296                let rn_bits = reg_to_bits(rn);
297                let shift_bits = *shift & 0x1F;
298
299                // LSR encoding
300                0xE1A00020 | (rd_bits << 12) | (shift_bits << 7) | rn_bits
301            }
302
303            ArmOp::Asr { rd, rn, shift } => {
304                let rd_bits = reg_to_bits(rd);
305                let rn_bits = reg_to_bits(rn);
306                let shift_bits = *shift & 0x1F;
307
308                // ASR encoding
309                0xE1A00040 | (rd_bits << 12) | (shift_bits << 7) | rn_bits
310            }
311
312            ArmOp::Ror { rd, rn, shift } => {
313                let rd_bits = reg_to_bits(rd);
314                let rn_bits = reg_to_bits(rn);
315                let shift_bits = *shift & 0x1F;
316
317                // ROR encoding: MOV with ROR shift
318                0xE1A00060 | (rd_bits << 12) | (shift_bits << 7) | rn_bits
319            }
320
321            // Register-based shifts (ARM32)
322            // LSL Rd, Rn, Rm: cond 0001101S 0000 Rd Rs 0001 Rn
323            ArmOp::LslReg { rd, rn, rm } => {
324                let rd_bits = reg_to_bits(rd);
325                let rn_bits = reg_to_bits(rn);
326                let rm_bits = reg_to_bits(rm);
327                0xE1A00010 | (rd_bits << 12) | (rm_bits << 8) | rn_bits
328            }
329            ArmOp::LsrReg { rd, rn, rm } => {
330                let rd_bits = reg_to_bits(rd);
331                let rn_bits = reg_to_bits(rn);
332                let rm_bits = reg_to_bits(rm);
333                0xE1A00030 | (rd_bits << 12) | (rm_bits << 8) | rn_bits
334            }
335            ArmOp::AsrReg { rd, rn, rm } => {
336                let rd_bits = reg_to_bits(rd);
337                let rn_bits = reg_to_bits(rn);
338                let rm_bits = reg_to_bits(rm);
339                0xE1A00050 | (rd_bits << 12) | (rm_bits << 8) | rn_bits
340            }
341            ArmOp::RorReg { rd, rn, rm } => {
342                let rd_bits = reg_to_bits(rd);
343                let rn_bits = reg_to_bits(rn);
344                let rm_bits = reg_to_bits(rm);
345                0xE1A00070 | (rd_bits << 12) | (rm_bits << 8) | rn_bits
346            }
347
348            // RSB (Reverse Subtract): Rd = imm - Rn
349            ArmOp::Rsb { rd, rn, imm } => {
350                let rd_bits = reg_to_bits(rd);
351                let rn_bits = reg_to_bits(rn);
352                // RSB encoding: cond(4) | 00 1 0011 S | Rn(4) | Rd(4) | imm12
353                // Opcode for RSB = 0011, I=1 (immediate), S=0
354                0xE2600000 | (rn_bits << 16) | (rd_bits << 12) | (*imm & 0xFF)
355            }
356
357            // Bit manipulation instructions
358            ArmOp::Clz { rd, rm } => {
359                let rd_bits = reg_to_bits(rd);
360                let rm_bits = reg_to_bits(rm);
361
362                // CLZ encoding: cond(4) | 00010110 | 1111 | Rd(4) | 1111 | 0001 | Rm(4)
363                // ARMv5T and above
364                0xE16F0F10 | (rd_bits << 12) | rm_bits
365            }
366
367            ArmOp::Rbit { rd, rm } => {
368                let rd_bits = reg_to_bits(rd);
369                let rm_bits = reg_to_bits(rm);
370
371                // RBIT encoding: cond(4) | 01101111 | 1111 | Rd(4) | 1111 | 0011 | Rm(4)
372                // ARMv6T2 and above
373                0xE6FF0F30 | (rd_bits << 12) | rm_bits
374            }
375
376            ArmOp::Sxtb { rd, rm } => {
377                let rd_bits = reg_to_bits(rd);
378                let rm_bits = reg_to_bits(rm);
379
380                // SXTB encoding: cond(4) | 01101010 | 1111 | Rd(4) | rotate(2) | 00 | 0111 | Rm(4)
381                // ARMv6 and above. rotate=00 for no rotation
382                0xE6AF0070 | (rd_bits << 12) | rm_bits
383            }
384
385            ArmOp::Sxth { rd, rm } => {
386                let rd_bits = reg_to_bits(rd);
387                let rm_bits = reg_to_bits(rm);
388
389                // SXTH encoding: cond(4) | 01101011 | 1111 | Rd(4) | rotate(2) | 00 | 0111 | Rm(4)
390                // ARMv6 and above. rotate=00 for no rotation
391                0xE6BF0070 | (rd_bits << 12) | rm_bits
392            }
393
394            // Move instructions
395            ArmOp::Mov { rd, op2 } => {
396                let rd_bits = reg_to_bits(rd);
397                let (op2_bits, i_flag) = encode_operand2(op2);
398
399                // MOV encoding: opcode=1101
400                0xE1A00000 | (i_flag << 25) | (rd_bits << 12) | op2_bits
401            }
402
403            ArmOp::Mvn { rd, op2 } => {
404                let rd_bits = reg_to_bits(rd);
405                let (op2_bits, i_flag) = encode_operand2(op2);
406
407                // MVN encoding: opcode=1111
408                0xE1E00000 | (i_flag << 25) | (rd_bits << 12) | op2_bits
409            }
410
411            // MOVW - Move Wide (ARM32)
412            // Encoding: cond(4) | 0011 0000 | imm4(4) | Rd(4) | imm12(12)
413            ArmOp::Movw { rd, imm16 } => {
414                let rd_bits = reg_to_bits(rd);
415                let imm4 = ((*imm16 as u32) >> 12) & 0xF;
416                let imm12 = (*imm16 as u32) & 0xFFF;
417                0xE3000000 | (imm4 << 16) | (rd_bits << 12) | imm12
418            }
419
420            // MOVT - Move Top (ARM32)
421            // Encoding: cond(4) | 0011 0100 | imm4(4) | Rd(4) | imm12(12)
422            ArmOp::Movt { rd, imm16 } => {
423                let rd_bits = reg_to_bits(rd);
424                let imm4 = ((*imm16 as u32) >> 12) & 0xF;
425                let imm12 = (*imm16 as u32) & 0xFFF;
426                0xE3400000 | (imm4 << 16) | (rd_bits << 12) | imm12
427            }
428
429            // #237: symbol-relative MOVW/MOVT (ARM mode) — addend in place, the
430            // backend records the MOVW_ABS/MOVT_ABS relocation against `symbol`.
431            ArmOp::MovwSym { rd, addend, .. } => {
432                let rd_bits = reg_to_bits(rd);
433                let v = (*addend as u32) & 0xffff;
434                0xE3000000 | (((v >> 12) & 0xF) << 16) | (rd_bits << 12) | (v & 0xFFF)
435            }
436            ArmOp::MovtSym { rd, addend, .. } => {
437                let rd_bits = reg_to_bits(rd);
438                let v = ((*addend as u32) >> 16) & 0xffff;
439                0xE3400000 | (((v >> 12) & 0xF) << 16) | (rd_bits << 12) | (v & 0xFFF)
440            }
441
442            // Compare
443            ArmOp::Cmp { rn, op2 } => {
444                let rn_bits = reg_to_bits(rn);
445                let (op2_bits, i_flag) = encode_operand2(op2);
446
447                // CMP encoding: opcode=1010, S=1
448                0xE1500000 | (i_flag << 25) | (rn_bits << 16) | op2_bits
449            }
450
451            // Compare Negative (CMN) - computes Rn + op2 and sets flags
452            ArmOp::Cmn { rn, op2 } => {
453                let rn_bits = reg_to_bits(rn);
454                let (op2_bits, i_flag) = encode_operand2(op2);
455
456                // CMN encoding: opcode=1011, S=1
457                0xE1700000 | (i_flag << 25) | (rn_bits << 16) | op2_bits
458            }
459
460            // Load/Store
461            ArmOp::Ldr { rd, addr } => {
462                let rd_bits = reg_to_bits(rd);
463                let (base_bits, offset_bits) = encode_mem_addr(addr);
464
465                // LDR encoding: cond(4) | 01 | I(1) | P(1) | U(1) | B(1) | W(1) | L(1) | Rn(4) | Rd(4) | offset(12)
466                // P=1 (pre-indexed), U=1 (add offset), L=1 (load)
467                0xE5900000 | (base_bits << 16) | (rd_bits << 12) | offset_bits
468            }
469
470            ArmOp::Str { rd, addr } => {
471                let rd_bits = reg_to_bits(rd);
472                let (base_bits, offset_bits) = encode_mem_addr(addr);
473
474                // STR encoding: L=0 (store)
475                0xE5800000 | (base_bits << 16) | (rd_bits << 12) | offset_bits
476            }
477
478            // Sub-word loads (ARM32 encoding)
479            ArmOp::Ldrb { rd, addr } => {
480                let rd_bits = reg_to_bits(rd);
481                let (base_bits, offset_bits) = encode_mem_addr(addr);
482                // LDRB: LDR with B=1 (byte): cond|01|I|P|U|1|W|L|Rn|Rd|offset
483                0xE5D00000 | (base_bits << 16) | (rd_bits << 12) | offset_bits
484            }
485
486            ArmOp::Ldrsb { rd, addr } => {
487                let rd_bits = reg_to_bits(rd);
488                let (base_bits, offset_bits) = encode_mem_addr(addr);
489                // LDRSB (misc load): cond|000|P|U|1|W|1|Rn|Rd|imm4H|1101|imm4L
490                // Simplified with immediate offset
491                let offset_val = offset_bits & 0xFF;
492                let imm4h = (offset_val >> 4) & 0xF;
493                let imm4l = offset_val & 0xF;
494                0xE1D000D0 | (base_bits << 16) | (rd_bits << 12) | (imm4h << 8) | imm4l
495            }
496
497            ArmOp::Ldrh { rd, addr } => {
498                let rd_bits = reg_to_bits(rd);
499                let (base_bits, offset_bits) = encode_mem_addr(addr);
500                // LDRH (misc load): cond|000|P|U|1|W|1|Rn|Rd|imm4H|1011|imm4L
501                let offset_val = offset_bits & 0xFF;
502                let imm4h = (offset_val >> 4) & 0xF;
503                let imm4l = offset_val & 0xF;
504                0xE1D000B0 | (base_bits << 16) | (rd_bits << 12) | (imm4h << 8) | imm4l
505            }
506
507            ArmOp::Ldrsh { rd, addr } => {
508                let rd_bits = reg_to_bits(rd);
509                let (base_bits, offset_bits) = encode_mem_addr(addr);
510                // LDRSH (misc load): cond|000|P|U|1|W|1|Rn|Rd|imm4H|1111|imm4L
511                let offset_val = offset_bits & 0xFF;
512                let imm4h = (offset_val >> 4) & 0xF;
513                let imm4l = offset_val & 0xF;
514                0xE1D000F0 | (base_bits << 16) | (rd_bits << 12) | (imm4h << 8) | imm4l
515            }
516
517            // Sub-word stores (ARM32 encoding)
518            ArmOp::Strb { rd, addr } => {
519                let rd_bits = reg_to_bits(rd);
520                let (base_bits, offset_bits) = encode_mem_addr(addr);
521                // STRB: STR with B=1 (byte): cond|01|I|P|U|1|W|0|Rn|Rd|offset
522                0xE5C00000 | (base_bits << 16) | (rd_bits << 12) | offset_bits
523            }
524
525            ArmOp::Strh { rd, addr } => {
526                let rd_bits = reg_to_bits(rd);
527                let (base_bits, offset_bits) = encode_mem_addr(addr);
528                // STRH (misc store): cond|000|P|U|1|W|0|Rn|Rd|imm4H|1011|imm4L
529                let offset_val = offset_bits & 0xFF;
530                let imm4h = (offset_val >> 4) & 0xF;
531                let imm4l = offset_val & 0xF;
532                0xE1C000B0 | (base_bits << 16) | (rd_bits << 12) | (imm4h << 8) | imm4l
533            }
534
535            // Memory management (ARM32 encoding)
536            ArmOp::MemorySize { rd } => {
537                let rd_bits = reg_to_bits(rd);
538                // MOV rd, R10, LSR #16  (memory size in bytes / 65536 = pages)
539                // cond|000|1101|S|0000|Rd|shift5|type|0|Rm
540                // LSR #16: shift5=10000, type=01
541                0xE1A00820 | (rd_bits << 12) | 0x0A // Rm=R10, shift=16, LSR
542            }
543
544            ArmOp::MemoryGrow { rd, .. } => {
545                let rd_bits = reg_to_bits(rd);
546                // On embedded, always fail: MOV rd, #-1
547                0xE3E00000 | (rd_bits << 12) // MVN rd, #0 = MOV rd, #-1
548            }
549
550            // Label pseudo-instruction: emits no machine code
551            ArmOp::Label { .. } => {
552                return Ok(Vec::new());
553            }
554
555            // Branch instructions
556            ArmOp::B { label: _ } => {
557                // B encoding: cond(4) | 1010 | offset(24)
558                // Simplified: branch to offset 0 (will be patched by linker/resolver)
559                0xEA000000
560            }
561
562            // Conditional branch to label (generic)
563            ArmOp::Bcc { cond, label: _ } => {
564                use synth_synthesis::Condition;
565                let cond_bits: u32 = match cond {
566                    Condition::EQ => 0x0,
567                    Condition::NE => 0x1,
568                    Condition::HS => 0x2,
569                    Condition::LO => 0x3,
570                    Condition::HI => 0x8,
571                    Condition::LS => 0x9,
572                    Condition::GE => 0xA,
573                    Condition::LT => 0xB,
574                    Condition::GT => 0xC,
575                    Condition::LE => 0xD,
576                };
577                // B<cond> with offset 0 (will be patched)
578                (cond_bits << 28) | 0x0A000000
579            }
580
581            // BHS (Branch if Higher or Same) - used for bounds checking
582            ArmOp::Bhs { label: _ } => {
583                // BHS encoding: cond(2=HS) | 1010 | offset(24)
584                0x2A000000 // BHS with offset 0
585            }
586
587            // BLO (Branch if Lower) - complementary to BHS
588            ArmOp::Blo { label: _ } => {
589                // BLO encoding: cond(3=LO) | 1010 | offset(24)
590                0x3A000000 // BLO with offset 0
591            }
592
593            // Branch with numeric offset (in instructions)
594            // ARM32 B instruction: offset is in instructions, stored as words
595            // The offset is relative to PC+8 (due to ARM pipeline)
596            ArmOp::BOffset { offset } => {
597                // B encoding: cond(4) | 1010 | offset(24)
598                // Offset is signed, in words (4-byte units)
599                // ARM adds PC+8 to the offset, so we need to adjust:
600                // target = PC + 8 + (offset * 4)
601                // For backward branch of N instructions: offset = -(N + 2)
602                // wrapping_sub keeps the encoder total under fuzzing (#186): an
603                // extreme i32::MIN offset would otherwise overflow-panic; for any
604                // real branch offset this is identical to `- 2`.
605                let adjusted_offset = offset.wrapping_sub(2); // Account for PC+8
606                let offset_bits = (adjusted_offset as u32) & 0x00FFFFFF;
607                0xEA000000 | offset_bits
608            }
609
610            // Conditional branch with numeric offset
611            ArmOp::BCondOffset { cond, offset } => {
612                use synth_synthesis::Condition;
613                let cond_bits: u32 = match cond {
614                    Condition::EQ => 0x0,
615                    Condition::NE => 0x1,
616                    Condition::HS => 0x2,
617                    Condition::LO => 0x3,
618                    Condition::HI => 0x8,
619                    Condition::LS => 0x9,
620                    Condition::GE => 0xA,
621                    Condition::LT => 0xB,
622                    Condition::GT => 0xC,
623                    Condition::LE => 0xD,
624                };
625                // B<cond> encoding: cond(4) | 1010 | offset(24)
626                // wrapping_sub: total under fuzzing (#186), identical for real offsets.
627                let adjusted_offset = offset.wrapping_sub(2); // Account for PC+8
628                let offset_bits = (adjusted_offset as u32) & 0x00FFFFFF;
629                (cond_bits << 28) | 0x0A000000 | offset_bits
630            }
631
632            ArmOp::Bl { label: _ } => {
633                // BL encoding: cond(4) | 1011 | offset(24)
634                0xEB000000
635            }
636
637            ArmOp::Bx { rm } => {
638                let rm_bits = reg_to_bits(rm);
639
640                // BX encoding: cond(4) | 000100101111111111110001 | Rm(4)
641                0xE12FFF10 | rm_bits
642            }
643
644            ArmOp::Blx { rm } => {
645                let rm_bits = reg_to_bits(rm);
646
647                // BLX (register) encoding: cond(4) | 000100101111111111110011 | Rm(4)
648                0xE12FFF30 | rm_bits
649            }
650
651            ArmOp::Push { regs } => {
652                // STMDB SP!, {regs} encoding: cond(4) | 100100 | 10 | 1101 | register_list(16)
653                let mut reg_list: u32 = 0;
654                for r in regs {
655                    reg_list |= 1 << reg_to_bits(r);
656                }
657                0xE92D0000 | reg_list
658            }
659
660            ArmOp::Pop { regs } => {
661                // LDMIA SP!, {regs} encoding: cond(4) | 100010 | 11 | 1101 | register_list(16)
662                let mut reg_list: u32 = 0;
663                for r in regs {
664                    reg_list |= 1 << reg_to_bits(r);
665                }
666                0xE8BD0000 | reg_list
667            }
668
669            ArmOp::Nop => {
670                // NOP encoding: MOV R0, R0
671                0xE1A00000
672            }
673
674            ArmOp::Udf { imm } => {
675                // UDF (Undefined) encoding in ARM: 0xE7F000F0 | (imm12_hi << 8) | imm4_lo
676                // We only use imm8, so split into imm4_hi and imm4_lo
677                let imm8 = *imm as u32;
678                0xE7F000F0 | ((imm8 & 0xF0) << 4) | (imm8 & 0x0F)
679            }
680
681            // Pseudo-instructions for verification - encode as NOP
682            // These are used in formal verification but not actual code generation
683            ArmOp::Popcnt { .. } => {
684                // Population count pseudo-instruction
685                // Not a real ARM instruction, would be expanded to actual code
686                0xE1A00000 // NOP for now
687            }
688
689            ArmOp::SetCond { .. } => {
690                // Condition evaluation pseudo-instruction
691                // Not a real ARM instruction, would be expanded to actual code
692                0xE1A00000 // NOP for now
693            }
694
695            ArmOp::SelectMove { .. } => {
696                // Conditional move pseudo-instruction for ARM32
697                // Would use MOV{cond} instruction
698                0xE1A00000 // NOP for now
699            }
700
701            ArmOp::Select { .. } => {
702                // Select pseudo-instruction
703                // Not a real ARM instruction, would be expanded to conditional moves
704                0xE1A00000 // NOP for now
705            }
706
707            ArmOp::LocalGet { .. } => {
708                // Local variable get pseudo-instruction
709                // Not a real ARM instruction, would be expanded to memory access
710                0xE1A00000 // NOP for now
711            }
712
713            ArmOp::LocalSet { .. } => {
714                // Local variable set pseudo-instruction
715                // Not a real ARM instruction, would be expanded to memory access
716                0xE1A00000 // NOP for now
717            }
718
719            ArmOp::LocalTee { .. } => {
720                // Local variable tee pseudo-instruction
721                // Not a real ARM instruction, would be expanded to memory access
722                0xE1A00000 // NOP for now
723            }
724
725            ArmOp::GlobalGet { .. } => {
726                // Global variable get pseudo-instruction
727                // Not a real ARM instruction, would be expanded to memory access
728                0xE1A00000 // NOP for now
729            }
730
731            ArmOp::GlobalSet { .. } => {
732                // Global variable set pseudo-instruction
733                // Not a real ARM instruction, would be expanded to memory access
734                0xE1A00000 // NOP for now
735            }
736
737            ArmOp::BrTable { .. } => {
738                // Branch table pseudo-instruction
739                // Not a real ARM instruction, would be expanded to jump table
740                0xE1A00000 // NOP for now
741            }
742
743            ArmOp::Call { .. } => {
744                // Function call pseudo-instruction
745                // Not a real ARM instruction, would be expanded to BL
746                0xE1A00000 // NOP for now
747            }
748
749            ArmOp::CallIndirect { .. } => {
750                // Indirect function call pseudo-instruction
751                // Not a real ARM instruction, would be expanded to indirect branch
752                0xE1A00000 // NOP for now
753            }
754
755            // i64 pseudo-instructions (Phase 2) - encode as NOP for now
756            // Real compiler would expand these to multi-instruction sequences
757            ArmOp::I64Add { .. } => 0xE1A00000,        // NOP
758            ArmOp::I64Sub { .. } => 0xE1A00000,        // NOP
759            ArmOp::I64DivS { .. } => 0xE1A00000,       // NOP
760            ArmOp::I64DivU { .. } => 0xE1A00000,       // NOP
761            ArmOp::I64RemS { .. } => 0xE1A00000,       // NOP
762            ArmOp::I64RemU { .. } => 0xE1A00000,       // NOP
763            ArmOp::I64Clz { .. } => 0xE1A00000,        // NOP
764            ArmOp::I64Ctz { .. } => 0xE1A00000,        // NOP
765            ArmOp::I64Popcnt { .. } => 0xE1A00000,     // NOP
766            ArmOp::I64And { .. } => 0xE1A00000,        // NOP
767            ArmOp::I64Or { .. } => 0xE1A00000,         // NOP
768            ArmOp::I64Xor { .. } => 0xE1A00000,        // NOP
769            ArmOp::I64Eqz { .. } => 0xE1A00000,        // NOP
770            ArmOp::I64Eq { .. } => 0xE1A00000,         // NOP
771            ArmOp::I64Ne { .. } => 0xE1A00000,         // NOP
772            ArmOp::I64LtS { .. } => 0xE1A00000,        // NOP
773            ArmOp::I64LtU { .. } => 0xE1A00000,        // NOP
774            ArmOp::I64LeS { .. } => 0xE1A00000,        // NOP
775            ArmOp::I64LeU { .. } => 0xE1A00000,        // NOP
776            ArmOp::I64GtS { .. } => 0xE1A00000,        // NOP
777            ArmOp::I64GtU { .. } => 0xE1A00000,        // NOP
778            ArmOp::I64GeS { .. } => 0xE1A00000,        // NOP
779            ArmOp::I64GeU { .. } => 0xE1A00000,        // NOP
780            ArmOp::I64Const { .. } => 0xE1A00000,      // NOP
781            ArmOp::I64Ldr { .. } => 0xE1A00000,        // NOP
782            ArmOp::I64Str { .. } => 0xE1A00000,        // NOP
783            ArmOp::I64ExtendI32S { .. } => 0xE1A00000, // NOP
784            ArmOp::I64ExtendI32U { .. } => 0xE1A00000, // NOP
785            ArmOp::I64Extend8S { .. } => 0xE1A00000,   // NOP (Thumb-2 only)
786            ArmOp::I64Extend16S { .. } => 0xE1A00000,  // NOP (Thumb-2 only)
787            ArmOp::I64Extend32S { .. } => 0xE1A00000,  // NOP (Thumb-2 only)
788            ArmOp::I32WrapI64 { .. } => 0xE1A00000,    // NOP
789
790            // f32 VFP single-precision instructions
791            ArmOp::F32Add { sd, sn, sm } => encode_vfp_3reg(0xEE300A00, sd, sn, sm)?,
792            ArmOp::F32Sub { sd, sn, sm } => encode_vfp_3reg(0xEE300A40, sd, sn, sm)?,
793            ArmOp::F32Mul { sd, sn, sm } => encode_vfp_3reg(0xEE200A00, sd, sn, sm)?,
794            ArmOp::F32Div { sd, sn, sm } => encode_vfp_3reg(0xEE800A00, sd, sn, sm)?,
795            ArmOp::F32Abs { sd, sm } => encode_vfp_2reg(0xEEB00AC0, sd, sm)?,
796            ArmOp::F32Neg { sd, sm } => encode_vfp_2reg(0xEEB10A40, sd, sm)?,
797            ArmOp::F32Sqrt { sd, sm } => encode_vfp_2reg(0xEEB10AC0, sd, sm)?,
798
799            // f32 pseudo-ops — multi-instruction sequences
800            // FPSCR RMode: 00=nearest, 01=+inf(ceil), 10=-inf(floor), 11=zero(trunc)
801            ArmOp::F32Ceil { sd, sm } => {
802                return self.encode_arm_f32_rounding(sd, sm, 0b01); // Round toward +Inf
803            }
804            ArmOp::F32Floor { sd, sm } => {
805                return self.encode_arm_f32_rounding(sd, sm, 0b10); // Round toward -Inf
806            }
807            ArmOp::F32Trunc { sd, sm } => {
808                return self.encode_arm_f32_rounding(sd, sm, 0b11); // VCVT toward zero
809            }
810            ArmOp::F32Nearest { sd, sm } => {
811                return self.encode_arm_f32_rounding(sd, sm, 0b00); // VCVT to nearest
812            }
813            ArmOp::F32Min { sd, sn, sm } => {
814                return self.encode_arm_f32_minmax(sd, sn, sm, true);
815            }
816            ArmOp::F32Max { sd, sn, sm } => {
817                return self.encode_arm_f32_minmax(sd, sn, sm, false);
818            }
819            ArmOp::F32Copysign { sd, sn, sm } => {
820                return self.encode_arm_f32_copysign(sd, sn, sm);
821            }
822
823            // f32 comparisons — multi-instruction: VCMP + VMRS + conditional MOV
824            ArmOp::F32Eq { rd, sn, sm } => {
825                return self.encode_arm_f32_compare(rd, sn, sm, 0x0); // EQ
826            }
827            ArmOp::F32Ne { rd, sn, sm } => {
828                return self.encode_arm_f32_compare(rd, sn, sm, 0x1); // NE
829            }
830            ArmOp::F32Lt { rd, sn, sm } => {
831                return self.encode_arm_f32_compare(rd, sn, sm, 0x4); // MI (less than)
832            }
833            ArmOp::F32Le { rd, sn, sm } => {
834                return self.encode_arm_f32_compare(rd, sn, sm, 0x9); // LS (less or same)
835            }
836            ArmOp::F32Gt { rd, sn, sm } => {
837                return self.encode_arm_f32_compare(rd, sn, sm, 0xC); // GT
838            }
839            ArmOp::F32Ge { rd, sn, sm } => {
840                return self.encode_arm_f32_compare(rd, sn, sm, 0xA); // GE
841            }
842
843            // f32 const — multi-instruction: MOVW + MOVT + VMOV
844            ArmOp::F32Const { sd, value } => {
845                return self.encode_arm_f32_const(sd, *value);
846            }
847
848            ArmOp::F32Load { sd, addr } => encode_vfp_ldst(0xED900A00, sd, addr)?,
849            ArmOp::F32Store { sd, addr } => encode_vfp_ldst(0xED800A00, sd, addr)?,
850
851            // f32 conversions — multi-instruction sequences
852            ArmOp::F32ConvertI32S { sd, rm } => {
853                return self.encode_arm_f32_convert_i32(sd, rm, true);
854            }
855            ArmOp::F32ConvertI32U { sd, rm } => {
856                return self.encode_arm_f32_convert_i32(sd, rm, false);
857            }
858            ArmOp::F32ConvertI64S { .. } | ArmOp::F32ConvertI64U { .. } => {
859                return Err(synth_core::Error::synthesis(
860                    "F32 i64 conversion not supported (requires register pairs on 32-bit ARM)",
861                ));
862            }
863            ArmOp::F32ReinterpretI32 { sd, rm } => encode_vmov_core_sreg(true, sd, rm)?,
864            ArmOp::I32ReinterpretF32 { rd, sm } => encode_vmov_core_sreg(false, sm, rd)?,
865            ArmOp::I32TruncF32S { rd, sm } => {
866                return self.encode_arm_i32_trunc_f32(rd, sm, true);
867            }
868            ArmOp::I32TruncF32U { rd, sm } => {
869                return self.encode_arm_i32_trunc_f32(rd, sm, false);
870            }
871
872            // f64 VFP double-precision instructions (ARM32)
873            // F64 arithmetic: same as F32 but with sz=1 (bit 8 = 1, cp11 = 0xB)
874            ArmOp::F64Add { dd, dn, dm } => encode_vfp_3reg_f64(0xEE300B00, dd, dn, dm)?,
875            ArmOp::F64Sub { dd, dn, dm } => encode_vfp_3reg_f64(0xEE300B40, dd, dn, dm)?,
876            ArmOp::F64Mul { dd, dn, dm } => encode_vfp_3reg_f64(0xEE200B00, dd, dn, dm)?,
877            ArmOp::F64Div { dd, dn, dm } => encode_vfp_3reg_f64(0xEE800B00, dd, dn, dm)?,
878            ArmOp::F64Abs { dd, dm } => encode_vfp_2reg_f64(0xEEB00BC0, dd, dm)?,
879            ArmOp::F64Neg { dd, dm } => encode_vfp_2reg_f64(0xEEB10B40, dd, dm)?,
880            ArmOp::F64Sqrt { dd, dm } => encode_vfp_2reg_f64(0xEEB10BC0, dd, dm)?,
881
882            // f64 pseudo-ops
883            // FPSCR RMode: 00=nearest, 01=+inf(ceil), 10=-inf(floor), 11=zero(trunc)
884            ArmOp::F64Ceil { dd, dm } => {
885                return self.encode_arm_f64_rounding(dd, dm, 0b01);
886            }
887            ArmOp::F64Floor { dd, dm } => {
888                return self.encode_arm_f64_rounding(dd, dm, 0b10);
889            }
890            ArmOp::F64Trunc { dd, dm } => {
891                return self.encode_arm_f64_rounding(dd, dm, 0b11);
892            }
893            ArmOp::F64Nearest { dd, dm } => {
894                return self.encode_arm_f64_rounding(dd, dm, 0b00);
895            }
896            ArmOp::F64Min { dd, dn, dm } => {
897                return self.encode_arm_f64_minmax(dd, dn, dm, true);
898            }
899            ArmOp::F64Max { dd, dn, dm } => {
900                return self.encode_arm_f64_minmax(dd, dn, dm, false);
901            }
902            ArmOp::F64Copysign { dd, dn, dm } => {
903                return self.encode_arm_f64_copysign(dd, dn, dm);
904            }
905
906            // f64 comparisons
907            ArmOp::F64Eq { rd, dn, dm } => {
908                return self.encode_arm_f64_compare(rd, dn, dm, 0x0);
909            }
910            ArmOp::F64Ne { rd, dn, dm } => {
911                return self.encode_arm_f64_compare(rd, dn, dm, 0x1);
912            }
913            ArmOp::F64Lt { rd, dn, dm } => {
914                return self.encode_arm_f64_compare(rd, dn, dm, 0x4);
915            }
916            ArmOp::F64Le { rd, dn, dm } => {
917                return self.encode_arm_f64_compare(rd, dn, dm, 0x9);
918            }
919            ArmOp::F64Gt { rd, dn, dm } => {
920                return self.encode_arm_f64_compare(rd, dn, dm, 0xC);
921            }
922            ArmOp::F64Ge { rd, dn, dm } => {
923                return self.encode_arm_f64_compare(rd, dn, dm, 0xA);
924            }
925
926            ArmOp::F64Const { dd, value } => {
927                return self.encode_arm_f64_const(dd, *value);
928            }
929
930            ArmOp::F64Load { dd, addr } => encode_vfp_ldst_f64(0xED900B00, dd, addr)?,
931            ArmOp::F64Store { dd, addr } => encode_vfp_ldst_f64(0xED800B00, dd, addr)?,
932
933            ArmOp::F64ConvertI32S { dd, rm } => {
934                return self.encode_arm_f64_convert_i32(dd, rm, true);
935            }
936            ArmOp::F64ConvertI32U { dd, rm } => {
937                return self.encode_arm_f64_convert_i32(dd, rm, false);
938            }
939            ArmOp::F64ConvertI64S { .. } | ArmOp::F64ConvertI64U { .. } => {
940                return Err(synth_core::Error::synthesis(
941                    "F64 i64 conversion not supported (requires register pairs on 32-bit ARM)",
942                ));
943            }
944            ArmOp::F64PromoteF32 { dd, sm } => {
945                return self.encode_arm_f64_promote_f32(dd, sm);
946            }
947            ArmOp::F64ReinterpretI64 { dd, rmlo, rmhi } => {
948                encode_vmov_core_dreg(true, dd, rmlo, rmhi)?
949            }
950            ArmOp::I64ReinterpretF64 { rdlo, rdhi, dm } => {
951                encode_vmov_core_dreg(false, dm, rdlo, rdhi)?
952            }
953            ArmOp::I64TruncF64S { .. } | ArmOp::I64TruncF64U { .. } => {
954                return Err(synth_core::Error::synthesis(
955                    "i64 truncation from F64 not supported (requires i64 register pairs on 32-bit ARM)",
956                ));
957            }
958            ArmOp::I32TruncF64S { rd, dm } => {
959                return self.encode_arm_i32_trunc_f64(rd, dm, true);
960            }
961            ArmOp::I32TruncF64U { rd, dm } => {
962                return self.encode_arm_i32_trunc_f64(rd, dm, false);
963            }
964            // Multi-instruction sequences - only meaningful in Thumb-2 mode
965            ArmOp::I64SetCond { .. }
966            | ArmOp::I64SetCondZ { .. }
967            | ArmOp::I64Mul { .. }
968            | ArmOp::I64Shl { .. }
969            | ArmOp::I64ShrS { .. }
970            | ArmOp::I64ShrU { .. }
971            | ArmOp::I64Rotl { .. }
972            | ArmOp::I64Rotr { .. } => 0xE1A00000, // NOP (Thumb-2 only)
973
974            // MVE instructions — Thumb-2 only (Cortex-M55 is always Thumb-2)
975            ArmOp::MveLoad { .. }
976            | ArmOp::MveStore { .. }
977            | ArmOp::MveConst { .. }
978            | ArmOp::MveAnd { .. }
979            | ArmOp::MveOrr { .. }
980            | ArmOp::MveEor { .. }
981            | ArmOp::MveMvn { .. }
982            | ArmOp::MveBic { .. }
983            | ArmOp::MveAddI { .. }
984            | ArmOp::MveSubI { .. }
985            | ArmOp::MveMulI { .. }
986            | ArmOp::MveNegI { .. }
987            | ArmOp::MveCmpEqI { .. }
988            | ArmOp::MveCmpNeI { .. }
989            | ArmOp::MveCmpLtS { .. }
990            | ArmOp::MveCmpLtU { .. }
991            | ArmOp::MveCmpGtS { .. }
992            | ArmOp::MveCmpGtU { .. }
993            | ArmOp::MveCmpLeS { .. }
994            | ArmOp::MveCmpLeU { .. }
995            | ArmOp::MveCmpGeS { .. }
996            | ArmOp::MveCmpGeU { .. }
997            | ArmOp::MveDup { .. }
998            | ArmOp::MveExtractLane { .. }
999            | ArmOp::MveInsertLane { .. }
1000            | ArmOp::MveAddF32 { .. }
1001            | ArmOp::MveSubF32 { .. }
1002            | ArmOp::MveMulF32 { .. }
1003            | ArmOp::MveNegF32 { .. }
1004            | ArmOp::MveAbsF32 { .. }
1005            | ArmOp::MveCmpEqF32 { .. }
1006            | ArmOp::MveCmpNeF32 { .. }
1007            | ArmOp::MveCmpLtF32 { .. }
1008            | ArmOp::MveCmpLeF32 { .. }
1009            | ArmOp::MveCmpGtF32 { .. }
1010            | ArmOp::MveCmpGeF32 { .. }
1011            | ArmOp::MveDupF32 { .. }
1012            | ArmOp::MveExtractLaneF32 { .. }
1013            | ArmOp::MveReplaceLaneF32 { .. }
1014            | ArmOp::MveDivF32 { .. }
1015            | ArmOp::MveSqrtF32 { .. } => 0xE1A00000, // NOP (MVE = Thumb-2 only)
1016        };
1017
1018        // ARM32 instructions are little-endian
1019        Ok(instr.to_le_bytes().to_vec())
1020    }
1021
1022    // === ARM32 VFP multi-instruction helpers ===
1023
1024    /// Encode F32 comparison as ARM32: VCMP.F32 + VMRS + MOV rd,#0 + MOVcond rd,#1
1025    fn encode_arm_f32_compare(
1026        &self,
1027        rd: &Reg,
1028        sn: &VfpReg,
1029        sm: &VfpReg,
1030        cond_code: u32,
1031    ) -> Result<Vec<u8>> {
1032        let mut bytes = Vec::new();
1033
1034        // VCMP.F32 Sn, Sm: 0xEEB40A40 with Sn in Vd position, Sm in Vm position
1035        let sn_num = vfp_sreg_to_num(sn)?;
1036        let sm_num = vfp_sreg_to_num(sm)?;
1037        let (vd, d) = encode_sreg(sn_num);
1038        let (vm, m) = encode_sreg(sm_num);
1039        let vcmp = 0xEEB40A40 | (d << 22) | (vd << 12) | (m << 5) | vm;
1040        bytes.extend_from_slice(&vcmp.to_le_bytes());
1041
1042        // VMRS APSR_nzcv, FPSCR: 0xEEF1FA10
1043        bytes.extend_from_slice(&0xEEF1FA10u32.to_le_bytes());
1044
1045        // MOV rd, #0: 0xE3A0_0000 | (rd << 12)
1046        let rd_bits = reg_to_bits(rd);
1047        let mov_zero = 0xE3A00000 | (rd_bits << 12);
1048        bytes.extend_from_slice(&mov_zero.to_le_bytes());
1049
1050        // MOVcond rd, #1: cond(4) | 0011 1010 0000 rd(4) 0000 0000 0001
1051        let mov_one = (cond_code << 28) | 0x03A00001 | (rd_bits << 12);
1052        bytes.extend_from_slice(&mov_one.to_le_bytes());
1053
1054        Ok(bytes)
1055    }
1056
1057    /// Encode F32 constant load as ARM32: MOVW Rt,#lo16 + MOVT Rt,#hi16 + VMOV Sd,Rt
1058    fn encode_arm_f32_const(&self, sd: &VfpReg, value: f32) -> Result<Vec<u8>> {
1059        let mut bytes = Vec::new();
1060        let bits = value.to_bits();
1061
1062        // Use R12 as temp register for constant loading
1063        let rt: u32 = 12; // R12/IP
1064
1065        // MOVW R12, #lo16: 0xE300_C000 | (imm4 << 16) | imm12
1066        let lo16 = bits & 0xFFFF;
1067        let movw = 0xE3000000 | (rt << 12) | ((lo16 >> 12) << 16) | (lo16 & 0xFFF);
1068        bytes.extend_from_slice(&movw.to_le_bytes());
1069
1070        // MOVT R12, #hi16: 0xE340_C000 | (imm4 << 16) | imm12
1071        let hi16 = (bits >> 16) & 0xFFFF;
1072        let movt = 0xE3400000 | (rt << 12) | ((hi16 >> 12) << 16) | (hi16 & 0xFFF);
1073        bytes.extend_from_slice(&movt.to_le_bytes());
1074
1075        // VMOV Sd, R12
1076        let vmov = encode_vmov_core_sreg(true, sd, &Reg::R12)?;
1077        bytes.extend_from_slice(&vmov.to_le_bytes());
1078
1079        Ok(bytes)
1080    }
1081
1082    /// Encode VMOV + VCVT.F32.S32/U32 as ARM32
1083    fn encode_arm_f32_convert_i32(&self, sd: &VfpReg, rm: &Reg, signed: bool) -> Result<Vec<u8>> {
1084        let mut bytes = Vec::new();
1085
1086        // VMOV Sd, Rm — move integer to VFP register
1087        let vmov = encode_vmov_core_sreg(true, sd, rm)?;
1088        bytes.extend_from_slice(&vmov.to_le_bytes());
1089
1090        // VCVT.F32.S32 Sd, Sd (signed) or VCVT.F32.U32 Sd, Sd (unsigned)
1091        // Base: 0xEEB80A40 (signed) or 0xEEB80AC0 (unsigned)
1092        let sd_num = vfp_sreg_to_num(sd)?;
1093        let (vd, d) = encode_sreg(sd_num);
1094        let (vm, m) = encode_sreg(sd_num); // same register as source
1095        let base = if signed { 0xEEB80A40 } else { 0xEEB80AC0 };
1096        let vcvt = base | (d << 22) | (vd << 12) | (m << 5) | vm;
1097        bytes.extend_from_slice(&vcvt.to_le_bytes());
1098
1099        Ok(bytes)
1100    }
1101
1102    /// Encode F32 rounding pseudo-op as ARM32 via VCVT to integer and back.
1103    /// mode: 0b00=nearest, 0b01=floor(-Inf), 0b10=ceil(+Inf), 0b11=trunc(zero)
1104    /// Strategy: VCVT.S32.F32 Sd, Sm (toward zero), then VCVT.F32.S32 Sd, Sd
1105    /// For ceil/floor/nearest, we use VCVTR (round toward mode) + convert back.
1106    /// Simplified: convert to int (toward zero for trunc) then back to float.
1107    /// Encode F32 rounding as ARM32.
1108    /// `mode`: FPSCR RMode — 0b00=nearest, 0b01=+inf(ceil), 0b10=-inf(floor), 0b11=zero(trunc)
1109    ///
1110    /// For trunc (mode=0b11): uses VCVTR.S32.F32 (always rounds toward zero).
1111    /// For ceil/floor/nearest: sets FPSCR rounding mode, uses VCVT.S32.F32 (non-R variant
1112    /// which honours FPSCR rmode), then restores FPSCR.
1113    fn encode_arm_f32_rounding(&self, sd: &VfpReg, sm: &VfpReg, mode: u8) -> Result<Vec<u8>> {
1114        let mut bytes = Vec::new();
1115        let sm_num = vfp_sreg_to_num(sm)?;
1116        let sd_num = vfp_sreg_to_num(sd)?;
1117        let (vd_s, d_s) = encode_sreg(sd_num);
1118        let (vm_s, m_s) = encode_sreg(sm_num);
1119
1120        if mode == 0b11 {
1121            // Trunc (toward zero): VCVTR.S32.F32 — the "R" variant always truncates.
1122            // 0xEEBD0AC0: bit[7]=1 => round toward zero regardless of FPSCR
1123            let vcvt_to_int = 0xEEBD0AC0 | (d_s << 22) | (vd_s << 12) | (m_s << 5) | vm_s;
1124            bytes.extend_from_slice(&vcvt_to_int.to_le_bytes());
1125        } else {
1126            // ceil/floor/nearest: manipulate FPSCR rounding mode
1127            let rt: u32 = 12; // R12/IP as temp
1128
1129            // VMRS R12, FPSCR
1130            let vmrs = 0xEEF10A10 | (rt << 12);
1131            bytes.extend_from_slice(&vmrs.to_le_bytes());
1132
1133            // BIC R12, R12, #(3 << 22) — clear RMode bits [23:22]
1134            // 3<<22 = 0x00C00000. ARM rotated imm: 0x03 ror 10 (rotation=5, imm8=0x03)
1135            let bic = 0xE3CC0000 | (rt << 12) | (0x05 << 8) | 0x03;
1136            bytes.extend_from_slice(&bic.to_le_bytes());
1137
1138            // ORR R12, R12, #(mode << 22) — set desired rounding mode
1139            if mode != 0 {
1140                // mode<<22: rotation=5, imm8=mode
1141                let orr = 0xE38C0000 | (rt << 12) | (0x05 << 8) | (mode as u32);
1142                bytes.extend_from_slice(&orr.to_le_bytes());
1143            }
1144
1145            // VMSR FPSCR, R12
1146            let vmsr = 0xEEE10A10 | (rt << 12);
1147            bytes.extend_from_slice(&vmsr.to_le_bytes());
1148
1149            // VCVT.S32.F32 Sd, Sm — non-R variant (bit[7]=0), uses FPSCR rounding mode
1150            let vcvt_to_int = 0xEEBD0A40 | (d_s << 22) | (vd_s << 12) | (m_s << 5) | vm_s;
1151            bytes.extend_from_slice(&vcvt_to_int.to_le_bytes());
1152
1153            // Restore FPSCR: clear rmode bits back to nearest (default)
1154            bytes.extend_from_slice(&vmrs.to_le_bytes());
1155            bytes.extend_from_slice(&bic.to_le_bytes());
1156            bytes.extend_from_slice(&vmsr.to_le_bytes());
1157        }
1158
1159        // VCVT.F32.S32 Sd, Sd (convert integer result back to float)
1160        let (vd2, d2) = encode_sreg(sd_num);
1161        let vcvt_to_float = 0xEEB80A40 | (d2 << 22) | (vd2 << 12) | (d_s << 5) | vd_s;
1162        bytes.extend_from_slice(&vcvt_to_float.to_le_bytes());
1163
1164        Ok(bytes)
1165    }
1166
1167    /// Encode F32 min/max as ARM32: VCMP + VMRS + conditional VMOV
1168    fn encode_arm_f32_minmax(
1169        &self,
1170        sd: &VfpReg,
1171        sn: &VfpReg,
1172        sm: &VfpReg,
1173        is_min: bool,
1174    ) -> Result<Vec<u8>> {
1175        let mut bytes = Vec::new();
1176        let sn_num = vfp_sreg_to_num(sn)?;
1177        let sm_num = vfp_sreg_to_num(sm)?;
1178        let sd_num = vfp_sreg_to_num(sd)?;
1179
1180        // VMOV Sd, Sn (start with first operand)
1181        let (vd, d) = encode_sreg(sd_num);
1182        let (vn, n) = encode_sreg(sn_num);
1183        let vmov_sn = 0xEEB00A40 | (d << 22) | (vd << 12) | (n << 5) | vn;
1184        bytes.extend_from_slice(&vmov_sn.to_le_bytes());
1185
1186        // VCMP.F32 Sn, Sm
1187        let (vm, m) = encode_sreg(sm_num);
1188        let vcmp = 0xEEB40A40 | (n << 22) | (vn << 12) | (m << 5) | vm;
1189        bytes.extend_from_slice(&vcmp.to_le_bytes());
1190
1191        // VMRS APSR_nzcv, FPSCR
1192        bytes.extend_from_slice(&0xEEF1FA10u32.to_le_bytes());
1193
1194        // For min: if Sn > Sm (GT), use Sm. Condition = GT (0xC)
1195        // For max: if Sn < Sm (MI/LT), use Sm. Condition = MI (0x4)
1196        let cond = if is_min { 0xCu32 } else { 0x4u32 };
1197
1198        // VMOV{cond} Sd, Sm — conditional VMOV
1199        let vmov_cond = (cond << 28) | 0x0EB00A40 | (d << 22) | (vd << 12) | (m << 5) | vm;
1200        bytes.extend_from_slice(&vmov_cond.to_le_bytes());
1201
1202        Ok(bytes)
1203    }
1204
1205    /// Encode F32 copysign as ARM32: extract sign from Sm, magnitude from Sn
1206    fn encode_arm_f32_copysign(&self, sd: &VfpReg, sn: &VfpReg, sm: &VfpReg) -> Result<Vec<u8>> {
1207        let mut bytes = Vec::new();
1208
1209        // VMOV R12, Sm (get sign source bits)
1210        let vmov_sm = encode_vmov_core_sreg(false, sm, &Reg::R12)?;
1211        bytes.extend_from_slice(&vmov_sm.to_le_bytes());
1212
1213        // VMOV R0, Sn (get magnitude source bits) — use R0 as temp
1214        let vmov_sn = encode_vmov_core_sreg(false, sn, &Reg::R0)?;
1215        bytes.extend_from_slice(&vmov_sn.to_le_bytes());
1216
1217        // AND R12, R12, #0x80000000 (keep only sign bit)
1218        // Thumb-2 constant 0x80000000 needs special encoding; in ARM32 use rotated imm
1219        // 0x80000000 = 0x02 rotated right by 2 (rotation=1, imm8=0x02)
1220        let and_sign = 0xE2000000u32 | (12 << 16) | (12 << 12) | (1 << 8) | 0x02;
1221        bytes.extend_from_slice(&and_sign.to_le_bytes());
1222
1223        // BIC R0, R0, #0x80000000 (clear sign bit from magnitude)
1224        // R0 = register 0, so Rn and Rd fields are 0
1225        let bic_sign = 0xE3C00000u32 | (1 << 8) | 0x02;
1226        bytes.extend_from_slice(&bic_sign.to_le_bytes());
1227
1228        // ORR R0, R0, R12 (combine sign + magnitude)
1229        // R0 = register 0, so Rn and Rd fields are 0
1230        let orr = 0xE1800000u32 | 12;
1231        bytes.extend_from_slice(&orr.to_le_bytes());
1232
1233        // VMOV Sd, R0
1234        let vmov_result = encode_vmov_core_sreg(true, sd, &Reg::R0)?;
1235        bytes.extend_from_slice(&vmov_result.to_le_bytes());
1236
1237        Ok(bytes)
1238    }
1239
1240    /// Encode F64 comparison as ARM32: VCMP.F64 + VMRS + MOV rd,#0 + MOVcond rd,#1
1241    fn encode_arm_f64_compare(
1242        &self,
1243        rd: &Reg,
1244        dn: &VfpReg,
1245        dm: &VfpReg,
1246        cond_code: u32,
1247    ) -> Result<Vec<u8>> {
1248        let mut bytes = Vec::new();
1249
1250        // VCMP.F64 Dn, Dm: 0xEEB40B40 with Dn in Vd position, Dm in Vm position
1251        let dn_num = vfp_dreg_to_num(dn)?;
1252        let dm_num = vfp_dreg_to_num(dm)?;
1253        let (vd, d) = encode_dreg(dn_num);
1254        let (vm, m) = encode_dreg(dm_num);
1255        let vcmp = 0xEEB40B40 | (d << 22) | (vd << 12) | (m << 5) | vm;
1256        bytes.extend_from_slice(&vcmp.to_le_bytes());
1257
1258        // VMRS APSR_nzcv, FPSCR
1259        bytes.extend_from_slice(&0xEEF1FA10u32.to_le_bytes());
1260
1261        // MOV rd, #0
1262        let rd_bits = reg_to_bits(rd);
1263        let mov_zero = 0xE3A00000 | (rd_bits << 12);
1264        bytes.extend_from_slice(&mov_zero.to_le_bytes());
1265
1266        // MOVcond rd, #1
1267        let mov_one = (cond_code << 28) | 0x03A00001 | (rd_bits << 12);
1268        bytes.extend_from_slice(&mov_one.to_le_bytes());
1269
1270        Ok(bytes)
1271    }
1272
1273    /// Encode F64 constant load as ARM32: MOVW + MOVT + MOVW + MOVT + VMOV
1274    fn encode_arm_f64_const(&self, dd: &VfpReg, value: f64) -> Result<Vec<u8>> {
1275        let mut bytes = Vec::new();
1276        let bits = value.to_bits();
1277        let lo32 = bits as u32;
1278        let hi32 = (bits >> 32) as u32;
1279
1280        // Load low 32 bits into R0 (Rd field = 0 for R0)
1281        let lo16 = lo32 & 0xFFFF;
1282        let movw_r0 = 0xE3000000 | ((lo16 >> 12) << 16) | (lo16 & 0xFFF);
1283        bytes.extend_from_slice(&movw_r0.to_le_bytes());
1284        let hi16 = (lo32 >> 16) & 0xFFFF;
1285        let movt_r0 = 0xE3400000 | ((hi16 >> 12) << 16) | (hi16 & 0xFFF);
1286        bytes.extend_from_slice(&movt_r0.to_le_bytes());
1287
1288        // Load high 32 bits into R12
1289        let lo16 = hi32 & 0xFFFF;
1290        let movw_r12 = 0xE3000000 | ((lo16 >> 12) << 16) | (12 << 12) | (lo16 & 0xFFF);
1291        bytes.extend_from_slice(&movw_r12.to_le_bytes());
1292        let hi16 = (hi32 >> 16) & 0xFFFF;
1293        let movt_r12 = 0xE3400000 | ((hi16 >> 12) << 16) | (12 << 12) | (hi16 & 0xFFF);
1294        bytes.extend_from_slice(&movt_r12.to_le_bytes());
1295
1296        // VMOV Dd, R0, R12
1297        let vmov = encode_vmov_core_dreg(true, dd, &Reg::R0, &Reg::R12)?;
1298        bytes.extend_from_slice(&vmov.to_le_bytes());
1299
1300        Ok(bytes)
1301    }
1302
1303    /// Encode VMOV Sd, Rm + VCVT.F64.S32/U32 Dd, Sd as ARM32
1304    fn encode_arm_f64_convert_i32(&self, dd: &VfpReg, rm: &Reg, signed: bool) -> Result<Vec<u8>> {
1305        let mut bytes = Vec::new();
1306
1307        // Use S0 as intermediate: VMOV S0, Rm
1308        let vmov = encode_vmov_core_sreg(true, &VfpReg::S0, rm)?;
1309        bytes.extend_from_slice(&vmov.to_le_bytes());
1310
1311        // VCVT.F64.S32 Dd, S0 (signed) or VCVT.F64.U32 Dd, S0 (unsigned)
1312        // Base: 0xEEB80B40 (signed) or 0xEEB80BC0 (unsigned)
1313        let dd_num = vfp_dreg_to_num(dd)?;
1314        let (vd, d) = encode_dreg(dd_num);
1315        let base = if signed { 0xEEB80B40 } else { 0xEEB80BC0 };
1316        // S0 is register 0: Vm=0, M=0
1317        let vcvt = base | (d << 22) | (vd << 12);
1318        bytes.extend_from_slice(&vcvt.to_le_bytes());
1319
1320        Ok(bytes)
1321    }
1322
1323    /// Encode VCVT.F64.F32 Dd, Sm as ARM32 (f32 to f64 promotion)
1324    fn encode_arm_f64_promote_f32(&self, dd: &VfpReg, sm: &VfpReg) -> Result<Vec<u8>> {
1325        let dd_num = vfp_dreg_to_num(dd)?;
1326        let sm_num = vfp_sreg_to_num(sm)?;
1327        let (vd, d) = encode_dreg(dd_num);
1328        let (vm, m) = encode_sreg(sm_num);
1329
1330        // VCVT.F64.F32 Dd, Sm: 0xEEB70AC0
1331        let vcvt = 0xEEB70AC0 | (d << 22) | (vd << 12) | (m << 5) | vm;
1332        Ok(vcvt.to_le_bytes().to_vec())
1333    }
1334
1335    /// Encode VCVT.S32/U32.F64 Sd, Dm + VMOV Rd, Sd as ARM32
1336    fn encode_arm_i32_trunc_f64(&self, rd: &Reg, dm: &VfpReg, signed: bool) -> Result<Vec<u8>> {
1337        let mut bytes = Vec::new();
1338        let dm_num = vfp_dreg_to_num(dm)?;
1339        let (vm, m) = encode_dreg(dm_num);
1340
1341        // VCVT.S32.F64 S0, Dm (toward zero) or VCVT.U32.F64 S0, Dm
1342        // S0: Vd=0, D=0
1343        let base = if signed { 0xEEBD0BC0 } else { 0xEEBC0BC0 };
1344        let vcvt = base | (m << 5) | vm;
1345        bytes.extend_from_slice(&vcvt.to_le_bytes());
1346
1347        // VMOV Rd, S0
1348        let vmov = encode_vmov_core_sreg(false, &VfpReg::S0, rd)?;
1349        bytes.extend_from_slice(&vmov.to_le_bytes());
1350
1351        Ok(bytes)
1352    }
1353
1354    /// Encode F64 rounding pseudo-op as ARM32 via VCVT to integer and back.
1355    /// Encode F64 rounding as ARM32.
1356    /// `mode`: FPSCR RMode — 0b00=nearest, 0b01=+inf(ceil), 0b10=-inf(floor), 0b11=zero(trunc)
1357    ///
1358    /// For trunc: uses VCVTR.S32.F64 (always truncates).
1359    /// For ceil/floor/nearest: sets FPSCR rounding mode, uses VCVT.S32.F64 (non-R variant),
1360    /// then restores FPSCR.
1361    fn encode_arm_f64_rounding(&self, dd: &VfpReg, dm: &VfpReg, mode: u8) -> Result<Vec<u8>> {
1362        let mut bytes = Vec::new();
1363        let dm_num = vfp_dreg_to_num(dm)?;
1364        let dd_num = vfp_dreg_to_num(dd)?;
1365        let (vm, m) = encode_dreg(dm_num);
1366        let (vd, d) = encode_dreg(dd_num);
1367
1368        if mode == 0b11 {
1369            // Trunc (toward zero): VCVTR.S32.F64 — bit[7]=1, always truncates
1370            let vcvt_to_int = 0xEEBD0BC0 | (m << 5) | vm;
1371            bytes.extend_from_slice(&vcvt_to_int.to_le_bytes());
1372        } else {
1373            // ceil/floor/nearest: manipulate FPSCR rounding mode
1374            let rt: u32 = 12;
1375
1376            // VMRS R12, FPSCR
1377            let vmrs = 0xEEF10A10 | (rt << 12);
1378            bytes.extend_from_slice(&vmrs.to_le_bytes());
1379
1380            // BIC R12, R12, #(3 << 22)
1381            let bic = 0xE3CC0000 | (rt << 12) | (0x05 << 8) | 0x03;
1382            bytes.extend_from_slice(&bic.to_le_bytes());
1383
1384            // ORR R12, R12, #(mode << 22)
1385            if mode != 0 {
1386                let orr = 0xE38C0000 | (rt << 12) | (0x05 << 8) | (mode as u32);
1387                bytes.extend_from_slice(&orr.to_le_bytes());
1388            }
1389
1390            // VMSR FPSCR, R12
1391            let vmsr = 0xEEE10A10 | (rt << 12);
1392            bytes.extend_from_slice(&vmsr.to_le_bytes());
1393
1394            // VCVT.S32.F64 S0, Dm — non-R variant (bit[7]=0), uses FPSCR rmode
1395            let vcvt_to_int = 0xEEBD0B40 | (m << 5) | vm;
1396            bytes.extend_from_slice(&vcvt_to_int.to_le_bytes());
1397
1398            // Restore FPSCR
1399            bytes.extend_from_slice(&vmrs.to_le_bytes());
1400            bytes.extend_from_slice(&bic.to_le_bytes());
1401            bytes.extend_from_slice(&vmsr.to_le_bytes());
1402        }
1403
1404        // VCVT.F64.S32 Dd, S0 (convert back to double)
1405        let vcvt_to_float = 0xEEB80B40 | (d << 22) | (vd << 12);
1406        bytes.extend_from_slice(&vcvt_to_float.to_le_bytes());
1407
1408        Ok(bytes)
1409    }
1410
1411    /// Encode F64 min/max as ARM32: VMOV + VCMP + VMRS + conditional VMOV
1412    fn encode_arm_f64_minmax(
1413        &self,
1414        dd: &VfpReg,
1415        dn: &VfpReg,
1416        dm: &VfpReg,
1417        is_min: bool,
1418    ) -> Result<Vec<u8>> {
1419        let mut bytes = Vec::new();
1420        let dn_num = vfp_dreg_to_num(dn)?;
1421        let dm_num = vfp_dreg_to_num(dm)?;
1422        let dd_num = vfp_dreg_to_num(dd)?;
1423
1424        // VMOV.F64 Dd, Dn (start with first operand)
1425        let (vd, d) = encode_dreg(dd_num);
1426        let (vn, n) = encode_dreg(dn_num);
1427        let vmov_dn = 0xEEB00B40 | (d << 22) | (vd << 12) | (n << 5) | vn;
1428        bytes.extend_from_slice(&vmov_dn.to_le_bytes());
1429
1430        // VCMP.F64 Dn, Dm
1431        let (vm, m) = encode_dreg(dm_num);
1432        let vcmp = 0xEEB40B40 | (n << 22) | (vn << 12) | (m << 5) | vm;
1433        bytes.extend_from_slice(&vcmp.to_le_bytes());
1434
1435        // VMRS APSR_nzcv, FPSCR
1436        bytes.extend_from_slice(&0xEEF1FA10u32.to_le_bytes());
1437
1438        let cond = if is_min { 0xCu32 } else { 0x4u32 };
1439        let vmov_cond = (cond << 28) | 0x0EB00B40 | (d << 22) | (vd << 12) | (m << 5) | vm;
1440        bytes.extend_from_slice(&vmov_cond.to_le_bytes());
1441
1442        Ok(bytes)
1443    }
1444
1445    /// Encode F64 copysign as ARM32
1446    fn encode_arm_f64_copysign(&self, dd: &VfpReg, dn: &VfpReg, dm: &VfpReg) -> Result<Vec<u8>> {
1447        let mut bytes = Vec::new();
1448
1449        // VMOV R0, R12, Dm (get sign source bits)
1450        let vmov_dm = encode_vmov_core_dreg(false, dm, &Reg::R0, &Reg::R12)?;
1451        bytes.extend_from_slice(&vmov_dm.to_le_bytes());
1452
1453        // VMOV R1, R2, Dn (get magnitude source bits)
1454        // We use R1 (lo) and R2 (hi) for the magnitude
1455        let vmov_dn = encode_vmov_core_dreg(false, dn, &Reg::R1, &Reg::R2)?;
1456        bytes.extend_from_slice(&vmov_dn.to_le_bytes());
1457
1458        // AND R12, R12, #0x80000000 (keep only sign bit from hi word)
1459        let and_sign = 0xE2000000u32 | (12 << 16) | (12 << 12) | (1 << 8) | 0x02;
1460        bytes.extend_from_slice(&and_sign.to_le_bytes());
1461
1462        // BIC R2, R2, #0x80000000 (clear sign bit from magnitude hi word)
1463        let bic_sign = 0xE3C00000u32 | (2 << 16) | (2 << 12) | (1 << 8) | 0x02;
1464        bytes.extend_from_slice(&bic_sign.to_le_bytes());
1465
1466        // ORR R2, R2, R12 (combine sign + magnitude)
1467        let orr = 0xE1800000u32 | (2 << 16) | (2 << 12) | 12;
1468        bytes.extend_from_slice(&orr.to_le_bytes());
1469
1470        // VMOV Dd, R1, R2
1471        let vmov_result = encode_vmov_core_dreg(true, dd, &Reg::R1, &Reg::R2)?;
1472        bytes.extend_from_slice(&vmov_result.to_le_bytes());
1473
1474        Ok(bytes)
1475    }
1476
1477    /// Encode VCVT.S32/U32.F32 + VMOV as ARM32
1478    fn encode_arm_i32_trunc_f32(&self, rd: &Reg, sm: &VfpReg, signed: bool) -> Result<Vec<u8>> {
1479        let mut bytes = Vec::new();
1480
1481        // VCVT.S32.F32 Sd, Sm (toward zero) or VCVT.U32.F32 Sd, Sm
1482        // We use Sm as both source and destination for the intermediate result
1483        let sm_num = vfp_sreg_to_num(sm)?;
1484        let (vd, d) = encode_sreg(sm_num);
1485        let (vm, m) = encode_sreg(sm_num);
1486        let base = if signed { 0xEEBD0AC0 } else { 0xEEBC0AC0 };
1487        let vcvt = base | (d << 22) | (vd << 12) | (m << 5) | vm;
1488        bytes.extend_from_slice(&vcvt.to_le_bytes());
1489
1490        // VMOV Rd, Sm — move result back to core register
1491        let vmov = encode_vmov_core_sreg(false, sm, rd)?;
1492        bytes.extend_from_slice(&vmov.to_le_bytes());
1493
1494        Ok(bytes)
1495    }
1496
1497    /// Encode an ARM instruction in Thumb-2 mode (16-bit or 32-bit instructions)
1498    fn encode_thumb(&self, op: &ArmOp) -> Result<Vec<u8>> {
1499        // Thumb-2 supports both 16-bit and 32-bit instructions
1500        // 32-bit instructions are encoded as two 16-bit halfwords (big-endian order)
1501        match op {
1502            // === 16-bit Thumb encodings ===
1503            ArmOp::Add { rd, rn, op2 } => {
1504                let rd_bits = reg_to_bits(rd) as u16;
1505                let rn_bits = reg_to_bits(rn) as u16;
1506
1507                if let Operand2::Reg(rm) = op2 {
1508                    let rm_bits = reg_to_bits(rm) as u16;
1509                    // 16-bit ADDS only has 3-bit register fields (R0-R7). For
1510                    // high registers (e.g. R12, the MemLoad/MemStore base
1511                    // scratch) the bits overflow into adjacent fields, silently
1512                    // corrupting the operands — issue #178/#180: `add ip,ip,r0`
1513                    // was emitted as `adds r4,r5,r1`. Guard on all three regs
1514                    // being low and fall back to 32-bit ADD.W otherwise, exactly
1515                    // as the Sub handler below does.
1516                    if rd_bits < 8 && rn_bits < 8 && rm_bits < 8 {
1517                        // ADDS Rd, Rn, Rm (16-bit): 0001 100 Rm Rn Rd
1518                        let instr: u16 = 0x1800 | (rm_bits << 6) | (rn_bits << 3) | rd_bits;
1519                        Ok(instr.to_le_bytes().to_vec())
1520                    } else {
1521                        // ADD.W Rd, Rn, Rm (32-bit) for high registers
1522                        self.encode_thumb32_add_reg_raw(
1523                            rd_bits as u32,
1524                            rn_bits as u32,
1525                            rm_bits as u32,
1526                        )
1527                    }
1528                } else if let Operand2::Imm(imm) = op2 {
1529                    if *imm <= 7 && rd_bits < 8 && rn_bits < 8 {
1530                        // ADDS Rd, Rn, #imm3 (16-bit): 0001 110 imm3 Rn Rd
1531                        let instr: u16 = 0x1C00 | ((*imm as u16) << 6) | (rn_bits << 3) | rd_bits;
1532                        Ok(instr.to_le_bytes().to_vec())
1533                    } else {
1534                        // Use 32-bit ADD for larger immediates
1535                        self.encode_thumb32_add(rd, rn, *imm as u32)
1536                    }
1537                } else {
1538                    // Fallback to 32-bit encoding
1539                    self.encode_thumb32_add(rd, rn, 0)
1540                }
1541            }
1542
1543            ArmOp::Sub { rd, rn, op2 } => {
1544                let rd_bits = reg_to_bits(rd) as u16;
1545                let rn_bits = reg_to_bits(rn) as u16;
1546
1547                if let Operand2::Reg(rm) = op2 {
1548                    let rm_bits = reg_to_bits(rm) as u16;
1549                    // 16-bit SUBS can only use low registers (R0-R7)
1550                    if rd_bits < 8 && rn_bits < 8 && rm_bits < 8 {
1551                        // SUBS Rd, Rn, Rm (16-bit): 0001 101 Rm Rn Rd
1552                        let instr: u16 = 0x1A00 | (rm_bits << 6) | (rn_bits << 3) | rd_bits;
1553                        Ok(instr.to_le_bytes().to_vec())
1554                    } else {
1555                        // Use 32-bit SUB.W for high registers
1556                        self.encode_thumb32_sub_reg_raw(
1557                            rd_bits as u32,
1558                            rn_bits as u32,
1559                            rm_bits as u32,
1560                        )
1561                    }
1562                } else if let Operand2::Imm(imm) = op2 {
1563                    if *imm <= 7 && rd_bits < 8 && rn_bits < 8 {
1564                        // SUBS Rd, Rn, #imm3 (16-bit): 0001 111 imm3 Rn Rd
1565                        let instr: u16 = 0x1E00 | ((*imm as u16) << 6) | (rn_bits << 3) | rd_bits;
1566                        Ok(instr.to_le_bytes().to_vec())
1567                    } else {
1568                        self.encode_thumb32_sub(rd, rn, *imm as u32)
1569                    }
1570                } else {
1571                    self.encode_thumb32_sub(rd, rn, 0)
1572                }
1573            }
1574
1575            ArmOp::Mov { rd, op2 } => {
1576                let rd_bits = reg_to_bits(rd) as u16;
1577
1578                if let Operand2::Imm(imm) = op2 {
1579                    if *imm <= 255 && rd_bits < 8 {
1580                        // MOVS Rd, #imm8 (16-bit): 0010 0 Rd imm8
1581                        let imm_bits = (*imm as u16) & 0xFF;
1582                        let instr: u16 = 0x2000 | (rd_bits << 8) | imm_bits;
1583                        Ok(instr.to_le_bytes().to_vec())
1584                    } else {
1585                        // Use 32-bit MOVW for larger immediates
1586                        self.encode_thumb32_movw(rd, *imm as u32)
1587                    }
1588                } else if let Operand2::Reg(rm) = op2 {
1589                    let rm_bits = reg_to_bits(rm) as u16;
1590                    // MOV Rd, Rm (16-bit): 0100 0110 D Rm Rd[2:0]
1591                    // D = Rd[3], Rd[2:0] in lower bits
1592                    let d_bit = (rd_bits >> 3) & 1;
1593                    let instr: u16 = 0x4600 | (d_bit << 7) | (rm_bits << 3) | (rd_bits & 0x7);
1594                    Ok(instr.to_le_bytes().to_vec())
1595                } else {
1596                    let instr: u16 = 0xBF00; // NOP fallback
1597                    Ok(instr.to_le_bytes().to_vec())
1598                }
1599            }
1600
1601            ArmOp::Push { regs } => {
1602                // Thumb-2 PUSH encoding:
1603                // If all regs in R0-R7 + LR, use 16-bit: 1011 010 M rrrrrrrr
1604                // Otherwise use 32-bit: STMDB SP!, {regs} = 1110 1001 0010 1101 | 0M0 reglist(13)
1605                let mut reg_list: u16 = 0;
1606                let mut need_32bit = false;
1607                for r in regs {
1608                    let bit = reg_to_bits(r);
1609                    if bit >= 8 && *r != Reg::LR {
1610                        need_32bit = true;
1611                    }
1612                    reg_list |= 1 << bit;
1613                }
1614                if !need_32bit {
1615                    // 16-bit PUSH: 1011 010 M rrrrrrrr
1616                    let m_bit = if reg_list & (1 << 14) != 0 {
1617                        1u16
1618                    } else {
1619                        0u16
1620                    };
1621                    let low_regs = reg_list & 0xFF;
1622                    let instr: u16 = 0xB400 | (m_bit << 8) | low_regs;
1623                    Ok(instr.to_le_bytes().to_vec())
1624                } else {
1625                    // 32-bit STMDB SP!, {regs}: E92D | reglist(16)
1626                    let hw1: u16 = 0xE92D;
1627                    let hw2: u16 = reg_list;
1628                    let mut bytes = hw1.to_le_bytes().to_vec();
1629                    bytes.extend_from_slice(&hw2.to_le_bytes());
1630                    Ok(bytes)
1631                }
1632            }
1633
1634            ArmOp::Pop { regs } => {
1635                // Thumb-2 POP encoding:
1636                // If all regs in R0-R7 + PC, use 16-bit: 1011 110 P rrrrrrrr
1637                // Otherwise use 32-bit: LDMIA SP!, {regs} = 1110 1000 1011 1101 | PM0 reglist(13)
1638                let mut reg_list: u16 = 0;
1639                let mut need_32bit = false;
1640                for r in regs {
1641                    let bit = reg_to_bits(r);
1642                    if bit >= 8 && *r != Reg::PC {
1643                        need_32bit = true;
1644                    }
1645                    reg_list |= 1 << bit;
1646                }
1647                if !need_32bit {
1648                    // 16-bit POP: 1011 110 P rrrrrrrr
1649                    let p_bit = if reg_list & (1 << 15) != 0 {
1650                        1u16
1651                    } else {
1652                        0u16
1653                    };
1654                    let low_regs = reg_list & 0xFF;
1655                    let instr: u16 = 0xBC00 | (p_bit << 8) | low_regs;
1656                    Ok(instr.to_le_bytes().to_vec())
1657                } else {
1658                    // 32-bit LDMIA SP!, {regs}: E8BD | reglist(16)
1659                    let hw1: u16 = 0xE8BD;
1660                    let hw2: u16 = reg_list;
1661                    let mut bytes = hw1.to_le_bytes().to_vec();
1662                    bytes.extend_from_slice(&hw2.to_le_bytes());
1663                    Ok(bytes)
1664                }
1665            }
1666
1667            ArmOp::Nop => {
1668                let instr: u16 = 0xBF00; // NOP in Thumb-2
1669                Ok(instr.to_le_bytes().to_vec())
1670            }
1671
1672            ArmOp::Udf { imm } => {
1673                // UDF (Undefined) in Thumb-2: 16-bit encoding is 0xDE00 | imm8
1674                // This triggers UsageFault/HardFault, used for WASM traps
1675                let instr: u16 = 0xDE00 | (*imm as u16);
1676                let bytes = instr.to_le_bytes().to_vec();
1677                encoding_contracts::verify_thumb16(&bytes);
1678                Ok(bytes)
1679            }
1680
1681            // i64 support: ADDS, ADC, SUBS, SBC for register pair arithmetic
1682            // ADDS sets flags (carry), ADC uses carry from previous ADDS
1683            ArmOp::Adds { rd, rn, op2 } => {
1684                let rd_bits = reg_to_bits(rd) as u16;
1685                let rn_bits = reg_to_bits(rn) as u16;
1686
1687                if let Operand2::Reg(rm) = op2 {
1688                    let rm_bits = reg_to_bits(rm) as u16;
1689                    // 16-bit ADDS is R0-R7 only; i64 pair allocation can place
1690                    // operands in R8-R11, which would overflow the 3-bit fields
1691                    // and corrupt the operands (#178/#180 class). Guard and fall
1692                    // back to 32-bit ADDS.W for high registers.
1693                    if rd_bits < 8 && rn_bits < 8 && rm_bits < 8 {
1694                        // ADDS Rd, Rn, Rm (16-bit): 0001 100 Rm Rn Rd
1695                        let instr: u16 = 0x1800 | (rm_bits << 6) | (rn_bits << 3) | rd_bits;
1696                        Ok(instr.to_le_bytes().to_vec())
1697                    } else {
1698                        self.encode_thumb32_adds_reg_raw(
1699                            rd_bits as u32,
1700                            rn_bits as u32,
1701                            rm_bits as u32,
1702                        )
1703                    }
1704                } else {
1705                    // 32-bit Thumb-2 ADDS with immediate
1706                    self.encode_thumb32_adds(rd, rn, 0)
1707                }
1708            }
1709
1710            // ADC: Add with Carry (Thumb-2 32-bit)
1711            // ADC.W Rd, Rn, Rm: EB40 Rn | 00 Rd 00 Rm
1712            ArmOp::Adc { rd, rn, op2 } => {
1713                let rd_bits = reg_to_bits(rd);
1714                let rn_bits = reg_to_bits(rn);
1715
1716                if let Operand2::Reg(rm) = op2 {
1717                    let rm_bits = reg_to_bits(rm);
1718                    // ADC.W Rd, Rn, Rm (T2): 1110 1011 0100 Rn | 0 000 Rd 00 00 Rm
1719                    let hw1: u16 = (0xEB40 | rn_bits) as u16;
1720                    let hw2: u16 = ((rd_bits << 8) | rm_bits) as u16;
1721
1722                    let mut bytes = hw1.to_le_bytes().to_vec();
1723                    bytes.extend_from_slice(&hw2.to_le_bytes());
1724                    Ok(bytes)
1725                } else {
1726                    // ADC with immediate - use 32-bit encoding
1727                    let hw1: u16 = (0xF140 | rn_bits) as u16;
1728                    let hw2: u16 = (rd_bits << 8) as u16;
1729                    let mut bytes = hw1.to_le_bytes().to_vec();
1730                    bytes.extend_from_slice(&hw2.to_le_bytes());
1731                    Ok(bytes)
1732                }
1733            }
1734
1735            // SUBS sets flags (borrow), SBC uses borrow from previous SUBS
1736            ArmOp::Subs { rd, rn, op2 } => {
1737                let rd_bits = reg_to_bits(rd) as u16;
1738                let rn_bits = reg_to_bits(rn) as u16;
1739
1740                if let Operand2::Reg(rm) = op2 {
1741                    let rm_bits = reg_to_bits(rm) as u16;
1742                    // 16-bit SUBS is R0-R7 only; high-register i64 pair operands
1743                    // would overflow the 3-bit fields (#178/#180 class). Guard
1744                    // and fall back to 32-bit SUBS.W for high registers.
1745                    if rd_bits < 8 && rn_bits < 8 && rm_bits < 8 {
1746                        // SUBS Rd, Rn, Rm (16-bit): 0001 101 Rm Rn Rd
1747                        let instr: u16 = 0x1A00 | (rm_bits << 6) | (rn_bits << 3) | rd_bits;
1748                        Ok(instr.to_le_bytes().to_vec())
1749                    } else {
1750                        self.encode_thumb32_subs_reg_raw(
1751                            rd_bits as u32,
1752                            rn_bits as u32,
1753                            rm_bits as u32,
1754                        )
1755                    }
1756                } else {
1757                    // 32-bit Thumb-2 SUBS with immediate
1758                    self.encode_thumb32_subs(rd, rn, 0)
1759                }
1760            }
1761
1762            // SBC: Subtract with Carry (Thumb-2 32-bit)
1763            // SBC.W Rd, Rn, Rm: EB60 Rn | 00 Rd 00 Rm
1764            ArmOp::Sbc { rd, rn, op2 } => {
1765                let rd_bits = reg_to_bits(rd);
1766                let rn_bits = reg_to_bits(rn);
1767
1768                if let Operand2::Reg(rm) = op2 {
1769                    let rm_bits = reg_to_bits(rm);
1770                    // SBC.W Rd, Rn, Rm (T2): 1110 1011 0110 Rn | 0 000 Rd 00 00 Rm
1771                    let hw1: u16 = (0xEB60 | rn_bits) as u16;
1772                    let hw2: u16 = ((rd_bits << 8) | rm_bits) as u16;
1773
1774                    let mut bytes = hw1.to_le_bytes().to_vec();
1775                    bytes.extend_from_slice(&hw2.to_le_bytes());
1776                    Ok(bytes)
1777                } else {
1778                    // SBC with immediate - use 32-bit encoding
1779                    let hw1: u16 = (0xF160 | rn_bits) as u16;
1780                    let hw2: u16 = (rd_bits << 8) as u16;
1781                    let mut bytes = hw1.to_le_bytes().to_vec();
1782                    bytes.extend_from_slice(&hw2.to_le_bytes());
1783                    Ok(bytes)
1784                }
1785            }
1786
1787            // === 32-bit Thumb-2 encodings ===
1788
1789            // SDIV: 11111011 1001 Rn 1111 Rd 1111 Rm
1790            ArmOp::Sdiv { rd, rn, rm } => {
1791                let rd_bits = reg_to_bits(rd);
1792                let rn_bits = reg_to_bits(rn);
1793                let rm_bits = reg_to_bits(rm);
1794                reg_bits_checked(rd_bits)?;
1795                reg_bits_checked(rn_bits)?;
1796                reg_bits_checked(rm_bits)?;
1797
1798                // Thumb-2 SDIV: FB90 F0F0 | Rn<<16 | Rd<<8 | Rm
1799                // First halfword: 1111 1011 1001 Rn = 0xFB90 | Rn
1800                // Second halfword: 1111 Rd 1111 Rm = 0xF0F0 | Rd<<8 | Rm
1801                let hw1: u16 = (0xFB90 | rn_bits) as u16;
1802                let hw2: u16 = (0xF0F0 | (rd_bits << 8) | rm_bits) as u16;
1803
1804                // Thumb-2 32-bit instructions: first halfword, then second halfword (little-endian each)
1805                let mut bytes = hw1.to_le_bytes().to_vec();
1806                bytes.extend_from_slice(&hw2.to_le_bytes());
1807                encoding_contracts::verify_thumb32(&bytes);
1808                Ok(bytes)
1809            }
1810
1811            // UDIV: 11111011 1011 Rn 1111 Rd 1111 Rm
1812            ArmOp::Udiv { rd, rn, rm } => {
1813                let rd_bits = reg_to_bits(rd);
1814                let rn_bits = reg_to_bits(rn);
1815                let rm_bits = reg_to_bits(rm);
1816                reg_bits_checked(rd_bits)?;
1817                reg_bits_checked(rn_bits)?;
1818                reg_bits_checked(rm_bits)?;
1819
1820                // Thumb-2 UDIV: FBB0 F0F0 | Rn<<16 | Rd<<8 | Rm
1821                let hw1: u16 = (0xFBB0 | rn_bits) as u16;
1822                let hw2: u16 = (0xF0F0 | (rd_bits << 8) | rm_bits) as u16;
1823
1824                let mut bytes = hw1.to_le_bytes().to_vec();
1825                bytes.extend_from_slice(&hw2.to_le_bytes());
1826                encoding_contracts::verify_thumb32(&bytes);
1827                Ok(bytes)
1828            }
1829
1830            ArmOp::Umull { rdlo, rdhi, rn, rm } => {
1831                let rdlo_bits = reg_to_bits(rdlo);
1832                let rdhi_bits = reg_to_bits(rdhi);
1833                let rn_bits = reg_to_bits(rn);
1834                let rm_bits = reg_to_bits(rm);
1835                reg_bits_checked(rdlo_bits)?;
1836                reg_bits_checked(rdhi_bits)?;
1837                reg_bits_checked(rn_bits)?;
1838                reg_bits_checked(rm_bits)?;
1839
1840                // Thumb-2 UMULL: 1111 1011 1010 Rn | RdLo RdHi 0000 Rm
1841                let hw1: u16 = (0xFBA0 | rn_bits) as u16;
1842                let hw2: u16 = ((rdlo_bits << 12) | (rdhi_bits << 8) | rm_bits) as u16;
1843
1844                let mut bytes = hw1.to_le_bytes().to_vec();
1845                bytes.extend_from_slice(&hw2.to_le_bytes());
1846                encoding_contracts::verify_thumb32(&bytes);
1847                Ok(bytes)
1848            }
1849
1850            // MUL (Thumb-2 32-bit): MUL Rd, Rn, Rm
1851            ArmOp::Mul { rd, rn, rm } => {
1852                let rd_bits = reg_to_bits(rd);
1853                let rn_bits = reg_to_bits(rn);
1854                let rm_bits = reg_to_bits(rm);
1855
1856                // Thumb-2 MUL: FB00 F000 | Rn | Rd<<8 | Rm
1857                // 11111011 0000 Rn | 1111 Rd 0000 Rm
1858                let hw1: u16 = (0xFB00 | rn_bits) as u16;
1859                let hw2: u16 = (0xF000 | (rd_bits << 8) | rm_bits) as u16;
1860
1861                let mut bytes = hw1.to_le_bytes().to_vec();
1862                bytes.extend_from_slice(&hw2.to_le_bytes());
1863                Ok(bytes)
1864            }
1865
1866            // MLS: Rd = Ra - Rn * Rm
1867            ArmOp::Mls { rd, rn, rm, ra } => {
1868                let rd_bits = reg_to_bits(rd);
1869                let rn_bits = reg_to_bits(rn);
1870                let rm_bits = reg_to_bits(rm);
1871                let ra_bits = reg_to_bits(ra);
1872
1873                // Thumb-2 MLS: FB00 Rn | Ra Rd 0001 Rm
1874                // 11111011 0000 Rn | Ra Rd 0001 Rm
1875                let hw1: u16 = (0xFB00 | rn_bits) as u16;
1876                let hw2: u16 = ((ra_bits << 12) | (rd_bits << 8) | 0x10 | rm_bits) as u16;
1877
1878                let mut bytes = hw1.to_le_bytes().to_vec();
1879                bytes.extend_from_slice(&hw2.to_le_bytes());
1880                Ok(bytes)
1881            }
1882
1883            ArmOp::Mla { rd, rn, rm, ra } => {
1884                let rd_bits = reg_to_bits(rd);
1885                let rn_bits = reg_to_bits(rn);
1886                let rm_bits = reg_to_bits(rm);
1887                let ra_bits = reg_to_bits(ra);
1888
1889                // Thumb-2 MLA: FB00 Rn | Ra Rd 0000 Rm — same as MLS without the
1890                // bit-4 (0x10) op flag. rd = ra + rn*rm.
1891                let hw1: u16 = (0xFB00 | rn_bits) as u16;
1892                let hw2: u16 = ((ra_bits << 12) | (rd_bits << 8) | rm_bits) as u16;
1893
1894                let mut bytes = hw1.to_le_bytes().to_vec();
1895                bytes.extend_from_slice(&hw2.to_le_bytes());
1896                Ok(bytes)
1897            }
1898
1899            // AND (Thumb-2 32-bit)
1900            ArmOp::And { rd, rn, op2 } => {
1901                if let Operand2::Reg(rm) = op2 {
1902                    let rd_bits = reg_to_bits(rd);
1903                    let rn_bits = reg_to_bits(rn);
1904                    let rm_bits = reg_to_bits(rm);
1905
1906                    // Thumb-2 AND register: EA00 Rn | 0 Rd 00 00 Rm
1907                    let hw1: u16 = (0xEA00 | rn_bits) as u16;
1908                    let hw2: u16 = ((rd_bits << 8) | rm_bits) as u16;
1909
1910                    let mut bytes = hw1.to_le_bytes().to_vec();
1911                    bytes.extend_from_slice(&hw2.to_le_bytes());
1912                    Ok(bytes)
1913                } else if let Operand2::Imm(imm) = op2 {
1914                    let rd_bits = reg_to_bits(rd);
1915                    let rn_bits = reg_to_bits(rn);
1916
1917                    // Thumb-2 AND.W immediate T1: 11110 i 0 0000 S Rn | 0 imm3 Rd imm8.
1918                    // The i:imm3:imm8 field is a ThumbExpandImm modified immediate —
1919                    // encode it correctly (or error on an un-encodable value)
1920                    // rather than packing raw bits, closing the silent-miscompile
1921                    // class for AND alongside ORR/EOR (#251) / ADD/SUB (#253) /
1922                    // CMP (#255).
1923                    let field = try_thumb_expand_imm(*imm as u32).ok_or_else(|| {
1924                        synth_core::Error::synthesis(
1925                            "AND immediate is not a valid ThumbExpandImm — materialize into a register",
1926                        )
1927                    })?;
1928                    let i_bit = (field >> 11) & 1;
1929                    let imm3 = (field >> 8) & 0x7;
1930                    let imm8 = field & 0xFF;
1931
1932                    let hw1: u16 = (0xF000 | (i_bit << 10) | rn_bits) as u16;
1933                    let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
1934
1935                    let mut bytes = hw1.to_le_bytes().to_vec();
1936                    bytes.extend_from_slice(&hw2.to_le_bytes());
1937                    Ok(bytes)
1938                } else {
1939                    // RegShift variant - fallback to NOP
1940                    let instr: u16 = 0xBF00;
1941                    Ok(instr.to_le_bytes().to_vec())
1942                }
1943            }
1944
1945            // ORR (Thumb-2 32-bit)
1946            ArmOp::Orr { rd, rn, op2 } => {
1947                if let Operand2::Reg(rm) = op2 {
1948                    let rd_bits = reg_to_bits(rd);
1949                    let rn_bits = reg_to_bits(rn);
1950                    let rm_bits = reg_to_bits(rm);
1951
1952                    // Thumb-2 ORR: EA40 Rn | 0 Rd 00 00 Rm
1953                    let hw1: u16 = (0xEA40 | rn_bits) as u16;
1954                    let hw2: u16 = ((rd_bits << 8) | rm_bits) as u16;
1955
1956                    let mut bytes = hw1.to_le_bytes().to_vec();
1957                    bytes.extend_from_slice(&hw2.to_le_bytes());
1958                    Ok(bytes)
1959                } else if let Operand2::Imm(imm) = op2 {
1960                    // ORR.W immediate T1: 11110 i 0 0010 S Rn | 0 imm3 Rd imm8.
1961                    // Only the zero-extended byte form (imm <= 0xFF) is encoded;
1962                    // larger modified immediates need ThumbExpandImm — return an
1963                    // error rather than silently emit a NOP (Ok-or-Err, #180/#185).
1964                    let imm_val = *imm as u32;
1965                    if imm_val > 0xFF {
1966                        return Err(synth_core::Error::synthesis(
1967                            "ORR immediate > 0xFF requires ThumbExpandImm (not yet implemented)",
1968                        ));
1969                    }
1970                    let rd_bits = reg_to_bits(rd);
1971                    let rn_bits = reg_to_bits(rn);
1972                    let hw1: u16 = (0xF040 | rn_bits) as u16;
1973                    let hw2: u16 = ((rd_bits << 8) | (imm_val & 0xFF)) as u16;
1974                    let mut bytes = hw1.to_le_bytes().to_vec();
1975                    bytes.extend_from_slice(&hw2.to_le_bytes());
1976                    Ok(bytes)
1977                } else {
1978                    let instr: u16 = 0xBF00;
1979                    Ok(instr.to_le_bytes().to_vec())
1980                }
1981            }
1982
1983            // EOR (Thumb-2 32-bit)
1984            ArmOp::Eor { rd, rn, op2 } => {
1985                if let Operand2::Reg(rm) = op2 {
1986                    let rd_bits = reg_to_bits(rd);
1987                    let rn_bits = reg_to_bits(rn);
1988                    let rm_bits = reg_to_bits(rm);
1989
1990                    // Thumb-2 EOR: EA80 Rn | 0 Rd 00 00 Rm
1991                    let hw1: u16 = (0xEA80 | rn_bits) as u16;
1992                    let hw2: u16 = ((rd_bits << 8) | rm_bits) as u16;
1993
1994                    let mut bytes = hw1.to_le_bytes().to_vec();
1995                    bytes.extend_from_slice(&hw2.to_le_bytes());
1996                    Ok(bytes)
1997                } else if let Operand2::Imm(imm) = op2 {
1998                    // EOR.W immediate T1: 11110 i 0 0100 S Rn | 0 imm3 Rd imm8.
1999                    // Byte form only (imm <= 0xFF); larger needs ThumbExpandImm —
2000                    // error, not a silent NOP (Ok-or-Err, #180/#185).
2001                    let imm_val = *imm as u32;
2002                    if imm_val > 0xFF {
2003                        return Err(synth_core::Error::synthesis(
2004                            "EOR immediate > 0xFF requires ThumbExpandImm (not yet implemented)",
2005                        ));
2006                    }
2007                    let rd_bits = reg_to_bits(rd);
2008                    let rn_bits = reg_to_bits(rn);
2009                    let hw1: u16 = (0xF080 | rn_bits) as u16;
2010                    let hw2: u16 = ((rd_bits << 8) | (imm_val & 0xFF)) as u16;
2011                    let mut bytes = hw1.to_le_bytes().to_vec();
2012                    bytes.extend_from_slice(&hw2.to_le_bytes());
2013                    Ok(bytes)
2014                } else {
2015                    let instr: u16 = 0xBF00;
2016                    Ok(instr.to_le_bytes().to_vec())
2017                }
2018            }
2019
2020            // Shift operations (16-bit for low registers)
2021            ArmOp::Lsl { rd, rn, shift } => {
2022                let rd_bits = reg_to_bits(rd) as u16;
2023                let rn_bits = reg_to_bits(rn) as u16;
2024                let shift_bits = (*shift as u16) & 0x1F;
2025
2026                if rd_bits < 8 && rn_bits < 8 {
2027                    // LSLS Rd, Rm, #imm5 (16-bit): 0000 0 imm5 Rm Rd
2028                    let instr: u16 = (shift_bits << 6) | (rn_bits << 3) | rd_bits;
2029                    Ok(instr.to_le_bytes().to_vec())
2030                } else {
2031                    // Use 32-bit encoding for high registers
2032                    self.encode_thumb32_shift(rd, rn, *shift, 0b00) // LSL type
2033                }
2034            }
2035
2036            ArmOp::Lsr { rd, rn, shift } => {
2037                let rd_bits = reg_to_bits(rd) as u16;
2038                let rn_bits = reg_to_bits(rn) as u16;
2039                let shift_bits = (*shift as u16) & 0x1F;
2040
2041                if rd_bits < 8 && rn_bits < 8 && shift_bits > 0 {
2042                    // LSRS Rd, Rm, #imm5 (16-bit): 0000 1 imm5 Rm Rd
2043                    let instr: u16 = 0x0800 | (shift_bits << 6) | (rn_bits << 3) | rd_bits;
2044                    Ok(instr.to_le_bytes().to_vec())
2045                } else {
2046                    self.encode_thumb32_shift(rd, rn, *shift, 0b01) // LSR type
2047                }
2048            }
2049
2050            ArmOp::Asr { rd, rn, shift } => {
2051                let rd_bits = reg_to_bits(rd) as u16;
2052                let rn_bits = reg_to_bits(rn) as u16;
2053                let shift_bits = (*shift as u16) & 0x1F;
2054
2055                if rd_bits < 8 && rn_bits < 8 && shift_bits > 0 {
2056                    // ASRS Rd, Rm, #imm5 (16-bit): 0001 0 imm5 Rm Rd
2057                    let instr: u16 = 0x1000 | (shift_bits << 6) | (rn_bits << 3) | rd_bits;
2058                    Ok(instr.to_le_bytes().to_vec())
2059                } else {
2060                    self.encode_thumb32_shift(rd, rn, *shift, 0b10) // ASR type
2061                }
2062            }
2063
2064            ArmOp::Ror { rd, rn, shift } => {
2065                // ROR doesn't have a 16-bit immediate form, use 32-bit
2066                self.encode_thumb32_shift(rd, rn, *shift, 0b11) // ROR type
2067            }
2068
2069            // Register-based shifts (Thumb-2 32-bit)
2070            // Encoding: 11111010 0xxS Rn 1111 Rd 0000 Rm
2071            // xx = shift type: 00=LSL, 01=LSR, 10=ASR, 11=ROR
2072            ArmOp::LslReg { rd, rn, rm } => self.encode_thumb32_shift_reg(rd, rn, rm, 0b00),
2073            ArmOp::LsrReg { rd, rn, rm } => self.encode_thumb32_shift_reg(rd, rn, rm, 0b01),
2074            ArmOp::AsrReg { rd, rn, rm } => self.encode_thumb32_shift_reg(rd, rn, rm, 0b10),
2075            ArmOp::RorReg { rd, rn, rm } => self.encode_thumb32_shift_reg(rd, rn, rm, 0b11),
2076
2077            // RSB (Reverse Subtract): Rd = imm - Rn
2078            // Thumb-2 T2 encoding: 11110 i 0 1110 S Rn | 0 imm3 Rd imm8
2079            ArmOp::Rsb { rd, rn, imm } => {
2080                let rd_bits = reg_to_bits(rd);
2081                let rn_bits = reg_to_bits(rn);
2082                let imm_val = *imm;
2083
2084                let i_bit = (imm_val >> 11) & 1;
2085                let imm3 = (imm_val >> 8) & 0x7;
2086                let imm8 = imm_val & 0xFF;
2087
2088                // hw1: 11110 i 01110 0 Rn  (S=0)
2089                let hw1: u16 = (0xF1C0 | (i_bit << 10) | rn_bits) as u16;
2090                // hw2: 0 imm3 Rd imm8
2091                let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
2092
2093                let mut bytes = hw1.to_le_bytes().to_vec();
2094                bytes.extend_from_slice(&hw2.to_le_bytes());
2095                Ok(bytes)
2096            }
2097
2098            // CLZ (Thumb-2 32-bit)
2099            ArmOp::Clz { rd, rm } => {
2100                let rd_bits = reg_to_bits(rd);
2101                let rm_bits = reg_to_bits(rm);
2102
2103                // Thumb-2 CLZ: FAB0 Rm | F8 Rd Rm
2104                // 11111010 1011 Rm | 1111 1000 Rd Rm
2105                let hw1: u16 = (0xFAB0 | rm_bits) as u16;
2106                let hw2: u16 = (0xF080 | (rd_bits << 8) | rm_bits) as u16;
2107
2108                let mut bytes = hw1.to_le_bytes().to_vec();
2109                bytes.extend_from_slice(&hw2.to_le_bytes());
2110                Ok(bytes)
2111            }
2112
2113            // RBIT (Thumb-2 32-bit)
2114            ArmOp::Rbit { rd, rm } => {
2115                let rd_bits = reg_to_bits(rd);
2116                let rm_bits = reg_to_bits(rm);
2117
2118                // Thumb-2 RBIT: FA90 Rm | F0 Rd A0 Rm
2119                // 11111010 1001 Rm | 1111 Rd 1010 Rm
2120                let hw1: u16 = (0xFA90 | rm_bits) as u16;
2121                let hw2: u16 = (0xF0A0 | (rd_bits << 8) | rm_bits) as u16;
2122
2123                let mut bytes = hw1.to_le_bytes().to_vec();
2124                bytes.extend_from_slice(&hw2.to_le_bytes());
2125                Ok(bytes)
2126            }
2127
2128            // SXTB (16-bit for low registers)
2129            ArmOp::Sxtb { rd, rm } => {
2130                let rd_bits = reg_to_bits(rd) as u16;
2131                let rm_bits = reg_to_bits(rm) as u16;
2132
2133                if rd_bits < 8 && rm_bits < 8 {
2134                    // SXTB Rd, Rm (16-bit): 1011 0010 01 Rm Rd
2135                    let instr: u16 = 0xB240 | (rm_bits << 3) | rd_bits;
2136                    Ok(instr.to_le_bytes().to_vec())
2137                } else {
2138                    // Thumb-2 SXTB.W: FA4F F(rd)80 (rm)
2139                    // 11111010 0100 1111 | 1111 Rd 10 rotate Rm
2140                    let rd_bits32 = rd_bits as u32;
2141                    let rm_bits32 = rm_bits as u32;
2142                    let hw1: u16 = 0xFA4F;
2143                    let hw2: u16 = (0xF080 | (rd_bits32 << 8) | rm_bits32) as u16;
2144                    let mut bytes = hw1.to_le_bytes().to_vec();
2145                    bytes.extend_from_slice(&hw2.to_le_bytes());
2146                    Ok(bytes)
2147                }
2148            }
2149
2150            // SXTH (16-bit for low registers)
2151            ArmOp::Sxth { rd, rm } => {
2152                let rd_bits = reg_to_bits(rd) as u16;
2153                let rm_bits = reg_to_bits(rm) as u16;
2154
2155                if rd_bits < 8 && rm_bits < 8 {
2156                    // SXTH Rd, Rm (16-bit): 1011 0010 00 Rm Rd
2157                    let instr: u16 = 0xB200 | (rm_bits << 3) | rd_bits;
2158                    Ok(instr.to_le_bytes().to_vec())
2159                } else {
2160                    // Thumb-2 SXTH.W: FA0F F(rd)80 (rm)
2161                    // 11111010 0000 1111 | 1111 Rd 10 rotate Rm
2162                    let rd_bits32 = rd_bits as u32;
2163                    let rm_bits32 = rm_bits as u32;
2164                    let hw1: u16 = 0xFA0F;
2165                    let hw2: u16 = (0xF080 | (rd_bits32 << 8) | rm_bits32) as u16;
2166                    let mut bytes = hw1.to_le_bytes().to_vec();
2167                    bytes.extend_from_slice(&hw2.to_le_bytes());
2168                    Ok(bytes)
2169                }
2170            }
2171
2172            // CMP (can be 16-bit for low registers)
2173            ArmOp::Cmp { rn, op2 } => {
2174                let rn_bits = reg_to_bits(rn) as u16;
2175
2176                if let Operand2::Imm(imm) = op2 {
2177                    // Only use 16-bit encoding for non-negative immediates 0-255
2178                    // Negative immediates must use 32-bit encoding
2179                    if *imm >= 0 && *imm <= 255 && rn_bits < 8 {
2180                        // CMP Rn, #imm8 (16-bit): 0010 1 Rn imm8
2181                        let instr: u16 = 0x2800 | (rn_bits << 8) | (*imm as u16 & 0xFF);
2182                        Ok(instr.to_le_bytes().to_vec())
2183                    } else {
2184                        self.encode_thumb32_cmp_imm(rn, *imm as u32)
2185                    }
2186                } else if let Operand2::Reg(rm) = op2 {
2187                    let rm_bits = reg_to_bits(rm) as u16;
2188                    if rn_bits < 8 && rm_bits < 8 {
2189                        // CMP Rn, Rm (16-bit low): 0100 0010 10 Rm Rn
2190                        let instr: u16 = 0x4280 | (rm_bits << 3) | rn_bits;
2191                        Ok(instr.to_le_bytes().to_vec())
2192                    } else {
2193                        // CMP Rn, Rm (16-bit high): 0100 0101 N Rm Rn[2:0]
2194                        let n_bit = (rn_bits >> 3) & 1;
2195                        let instr: u16 = 0x4500 | (n_bit << 7) | (rm_bits << 3) | (rn_bits & 0x7);
2196                        Ok(instr.to_le_bytes().to_vec())
2197                    }
2198                } else {
2199                    let instr: u16 = 0xBF00;
2200                    Ok(instr.to_le_bytes().to_vec())
2201                }
2202            }
2203
2204            // CMN (Compare Negative) - computes Rn + op2 and sets flags
2205            // CMN Rn, #1 sets Z flag if Rn == -1 (since -1 + 1 = 0)
2206            ArmOp::Cmn { rn, op2 } => {
2207                let rn_bits = reg_to_bits(rn) as u16;
2208
2209                if let Operand2::Imm(imm) = op2 {
2210                    // CMN.W Rn, #imm (32-bit): i:imm3:imm8 is a ThumbExpandImm
2211                    // modified immediate (the field sits in imm3=hw2[14:12],
2212                    // imm8=hw2[7:0], i=hw1[10]). Encode it correctly, or error on
2213                    // an un-encodable value — replacing the old silent `0xBF00`
2214                    // NOP (the last of the silent-miscompile data-proc encoders).
2215                    let field = try_thumb_expand_imm(*imm as u32).ok_or_else(|| {
2216                        synth_core::Error::synthesis(
2217                            "CMN immediate is not a valid ThumbExpandImm — materialize into a register",
2218                        )
2219                    })?;
2220                    let i_bit = (field >> 11) & 1;
2221                    let imm3 = (field >> 8) & 0x7;
2222                    let imm8 = field & 0xFF;
2223                    let hw1: u16 = (0xF110 | (i_bit << 10) as u16) | rn_bits;
2224                    let hw2: u16 = (imm3 << 12) as u16 | 0x0F00 | imm8 as u16;
2225                    let mut bytes = hw1.to_le_bytes().to_vec();
2226                    bytes.extend_from_slice(&hw2.to_le_bytes());
2227                    Ok(bytes)
2228                } else if let Operand2::Reg(rm) = op2 {
2229                    let rm_bits = reg_to_bits(rm) as u16;
2230                    // 16-bit CMN (T1) only encodes R0-R7; high registers overflow
2231                    // the 3-bit fields and corrupt the operands (#184, the #180
2232                    // class). CMN has no high-register 16-bit form, so fall back
2233                    // to 32-bit CMN.W (T2): EB10 Rn | 0F00 Rm (ADD.W with S=1 and
2234                    // Rd discarded as PC/1111).
2235                    if rn_bits < 8 && rm_bits < 8 {
2236                        // CMN Rn, Rm (16-bit): 0100 0010 11 Rm Rn
2237                        let instr: u16 = 0x42C0 | (rm_bits << 3) | rn_bits;
2238                        Ok(instr.to_le_bytes().to_vec())
2239                    } else {
2240                        let hw1: u16 = 0xEB10 | rn_bits;
2241                        let hw2: u16 = 0x0F00 | rm_bits;
2242                        let mut bytes = hw1.to_le_bytes().to_vec();
2243                        bytes.extend_from_slice(&hw2.to_le_bytes());
2244                        Ok(bytes)
2245                    }
2246                } else {
2247                    Ok(vec![0xBF, 0x00])
2248                }
2249            }
2250
2251            // LDR (can be 16-bit for simple cases)
2252            ArmOp::Ldr { rd, addr } => {
2253                let rd_bits = reg_to_bits(rd);
2254                let base_bits = reg_to_bits(&addr.base);
2255
2256                // Handle register offset mode [base, Roff] or [base, Roff, #imm]
2257                if let Some(offset_reg) = &addr.offset_reg {
2258                    let rm_bits = reg_to_bits(offset_reg);
2259
2260                    // If there's also an immediate offset, we need to ADD it first
2261                    if addr.offset != 0 {
2262                        // Use R12 (IP) as scratch to avoid clobbering the address register
2263                        // ADD R12, Rm, #offset; LDR Rd, [base, R12]
2264                        let scratch = Reg::R12;
2265                        let mut bytes =
2266                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2267                        bytes.extend(self.encode_thumb32_ldr_reg(rd, &addr.base, &scratch)?);
2268                        return Ok(bytes);
2269                    }
2270
2271                    // Simple register offset: LDR Rd, [Rn, Rm]
2272                    // 16-bit: only if Rd, Rn, Rm < R8
2273                    if rd_bits < 8 && base_bits < 8 && rm_bits < 8 {
2274                        // LDR Rd, [Rn, Rm] (16-bit): 0101 100 Rm Rn Rd
2275                        let instr: u16 = 0x5800
2276                            | ((rm_bits as u16) << 6)
2277                            | ((base_bits as u16) << 3)
2278                            | (rd_bits as u16);
2279                        return Ok(instr.to_le_bytes().to_vec());
2280                    }
2281
2282                    // 32-bit register offset
2283                    return self.encode_thumb32_ldr_reg(rd, &addr.base, offset_reg);
2284                }
2285
2286                // Immediate offset mode [base, #imm]
2287                let offset = addr.offset as u32;
2288
2289                if rd_bits < 8 && base_bits < 8 && (offset & 0x3) == 0 && offset <= 124 {
2290                    // LDR Rd, [Rn, #imm5*4] (16-bit): 0110 1 imm5 Rn Rd
2291                    let imm5 = (offset >> 2) as u16;
2292                    let instr: u16 =
2293                        0x6800 | (imm5 << 6) | ((base_bits as u16) << 3) | (rd_bits as u16);
2294                    Ok(instr.to_le_bytes().to_vec())
2295                } else {
2296                    self.encode_thumb32_ldr(rd, &addr.base, offset)
2297                }
2298            }
2299
2300            // STR (can be 16-bit for simple cases)
2301            ArmOp::Str { rd, addr } => {
2302                let rd_bits = reg_to_bits(rd);
2303                let base_bits = reg_to_bits(&addr.base);
2304
2305                // Handle register offset mode [base, Roff] or [base, Roff, #imm]
2306                if let Some(offset_reg) = &addr.offset_reg {
2307                    let rm_bits = reg_to_bits(offset_reg);
2308
2309                    // If there's also an immediate offset, we need to ADD it first
2310                    if addr.offset != 0 {
2311                        // Use R12 (IP) as scratch to avoid clobbering the address register
2312                        // ADD R12, Rm, #offset; STR Rd, [base, R12]
2313                        let scratch = Reg::R12;
2314                        let mut bytes =
2315                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2316                        bytes.extend(self.encode_thumb32_str_reg(rd, &addr.base, &scratch)?);
2317                        return Ok(bytes);
2318                    }
2319
2320                    // Simple register offset: STR Rd, [Rn, Rm]
2321                    // 16-bit: only if Rd, Rn, Rm < R8
2322                    if rd_bits < 8 && base_bits < 8 && rm_bits < 8 {
2323                        // STR Rd, [Rn, Rm] (16-bit): 0101 000 Rm Rn Rd
2324                        let instr: u16 = 0x5000
2325                            | ((rm_bits as u16) << 6)
2326                            | ((base_bits as u16) << 3)
2327                            | (rd_bits as u16);
2328                        return Ok(instr.to_le_bytes().to_vec());
2329                    }
2330
2331                    // 32-bit register offset
2332                    return self.encode_thumb32_str_reg(rd, &addr.base, offset_reg);
2333                }
2334
2335                // Immediate offset mode [base, #imm]
2336                let offset = addr.offset as u32;
2337
2338                if rd_bits < 8 && base_bits < 8 && (offset & 0x3) == 0 && offset <= 124 {
2339                    // STR Rd, [Rn, #imm5*4] (16-bit): 0110 0 imm5 Rn Rd
2340                    let imm5 = (offset >> 2) as u16;
2341                    let instr: u16 =
2342                        0x6000 | (imm5 << 6) | ((base_bits as u16) << 3) | (rd_bits as u16);
2343                    Ok(instr.to_le_bytes().to_vec())
2344                } else {
2345                    self.encode_thumb32_str(rd, &addr.base, offset)
2346                }
2347            }
2348
2349            // LDRB (Thumb-2)
2350            ArmOp::Ldrb { rd, addr } => {
2351                let rd_bits = reg_to_bits(rd);
2352                let base_bits = reg_to_bits(&addr.base);
2353
2354                if let Some(offset_reg) = &addr.offset_reg {
2355                    if addr.offset != 0 {
2356                        let scratch = Reg::R12;
2357                        let mut bytes =
2358                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2359                        bytes.extend(self.encode_thumb32_ldrb_reg(rd, &addr.base, &scratch)?);
2360                        return Ok(bytes);
2361                    }
2362                    return self.encode_thumb32_ldrb_reg(rd, &addr.base, offset_reg);
2363                }
2364
2365                let offset = addr.offset as u32;
2366                if rd_bits < 8 && base_bits < 8 && offset <= 31 {
2367                    // LDRB Rd, [Rn, #imm5] (16-bit): 0111 1 imm5 Rn Rd
2368                    let instr: u16 = 0x7800
2369                        | ((offset as u16) << 6)
2370                        | ((base_bits as u16) << 3)
2371                        | (rd_bits as u16);
2372                    Ok(instr.to_le_bytes().to_vec())
2373                } else {
2374                    self.encode_thumb32_ldrb_imm(rd, &addr.base, offset)
2375                }
2376            }
2377
2378            // LDRSB (Thumb-2)
2379            ArmOp::Ldrsb { rd, addr } => {
2380                let rd_bits = reg_to_bits(rd);
2381                let base_bits = reg_to_bits(&addr.base);
2382
2383                if let Some(offset_reg) = &addr.offset_reg {
2384                    if addr.offset != 0 {
2385                        let scratch = Reg::R12;
2386                        let mut bytes =
2387                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2388                        bytes.extend(self.encode_thumb32_ldrsb_reg(rd, &addr.base, &scratch)?);
2389                        return Ok(bytes);
2390                    }
2391                    return self.encode_thumb32_ldrsb_reg(rd, &addr.base, offset_reg);
2392                }
2393
2394                let offset = addr.offset as u32;
2395                // LDRSB has no 16-bit immediate form (only register)
2396                // For 16-bit reg form: only if Rd, Rn, Rm < R8
2397                if rd_bits < 8 && base_bits < 8 && offset == 0 {
2398                    // No immediate 16-bit encoding for LDRSB; use 32-bit
2399                    self.encode_thumb32_ldrsb_imm(rd, &addr.base, offset)
2400                } else {
2401                    self.encode_thumb32_ldrsb_imm(rd, &addr.base, offset)
2402                }
2403            }
2404
2405            // LDRH (Thumb-2)
2406            ArmOp::Ldrh { rd, addr } => {
2407                let rd_bits = reg_to_bits(rd);
2408                let base_bits = reg_to_bits(&addr.base);
2409
2410                if let Some(offset_reg) = &addr.offset_reg {
2411                    if addr.offset != 0 {
2412                        let scratch = Reg::R12;
2413                        let mut bytes =
2414                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2415                        bytes.extend(self.encode_thumb32_ldrh_reg(rd, &addr.base, &scratch)?);
2416                        return Ok(bytes);
2417                    }
2418                    return self.encode_thumb32_ldrh_reg(rd, &addr.base, offset_reg);
2419                }
2420
2421                let offset = addr.offset as u32;
2422                if rd_bits < 8 && base_bits < 8 && (offset & 0x1) == 0 && offset <= 62 {
2423                    // LDRH Rd, [Rn, #imm5*2] (16-bit): 1000 1 imm5 Rn Rd
2424                    let imm5 = (offset >> 1) as u16;
2425                    let instr: u16 =
2426                        0x8800 | (imm5 << 6) | ((base_bits as u16) << 3) | (rd_bits as u16);
2427                    Ok(instr.to_le_bytes().to_vec())
2428                } else {
2429                    self.encode_thumb32_ldrh_imm(rd, &addr.base, offset)
2430                }
2431            }
2432
2433            // LDRSH (Thumb-2)
2434            ArmOp::Ldrsh { rd, addr } => {
2435                if let Some(offset_reg) = &addr.offset_reg {
2436                    if addr.offset != 0 {
2437                        let scratch = Reg::R12;
2438                        let mut bytes =
2439                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2440                        bytes.extend(self.encode_thumb32_ldrsh_reg(rd, &addr.base, &scratch)?);
2441                        return Ok(bytes);
2442                    }
2443                    return self.encode_thumb32_ldrsh_reg(rd, &addr.base, offset_reg);
2444                }
2445
2446                let offset = addr.offset as u32;
2447                self.encode_thumb32_ldrsh_imm(rd, &addr.base, offset)
2448            }
2449
2450            // STRB (Thumb-2)
2451            ArmOp::Strb { rd, addr } => {
2452                let rd_bits = reg_to_bits(rd);
2453                let base_bits = reg_to_bits(&addr.base);
2454
2455                if let Some(offset_reg) = &addr.offset_reg {
2456                    if addr.offset != 0 {
2457                        let scratch = Reg::R12;
2458                        let mut bytes =
2459                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2460                        bytes.extend(self.encode_thumb32_strb_reg(rd, &addr.base, &scratch)?);
2461                        return Ok(bytes);
2462                    }
2463                    return self.encode_thumb32_strb_reg(rd, &addr.base, offset_reg);
2464                }
2465
2466                let offset = addr.offset as u32;
2467                if rd_bits < 8 && base_bits < 8 && offset <= 31 {
2468                    // STRB Rd, [Rn, #imm5] (16-bit): 0111 0 imm5 Rn Rd
2469                    let instr: u16 = 0x7000
2470                        | ((offset as u16) << 6)
2471                        | ((base_bits as u16) << 3)
2472                        | (rd_bits as u16);
2473                    Ok(instr.to_le_bytes().to_vec())
2474                } else {
2475                    self.encode_thumb32_strb_imm(rd, &addr.base, offset)
2476                }
2477            }
2478
2479            // STRH (Thumb-2)
2480            ArmOp::Strh { rd, addr } => {
2481                let rd_bits = reg_to_bits(rd);
2482                let base_bits = reg_to_bits(&addr.base);
2483
2484                if let Some(offset_reg) = &addr.offset_reg {
2485                    if addr.offset != 0 {
2486                        let scratch = Reg::R12;
2487                        let mut bytes =
2488                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2489                        bytes.extend(self.encode_thumb32_strh_reg(rd, &addr.base, &scratch)?);
2490                        return Ok(bytes);
2491                    }
2492                    return self.encode_thumb32_strh_reg(rd, &addr.base, offset_reg);
2493                }
2494
2495                let offset = addr.offset as u32;
2496                if rd_bits < 8 && base_bits < 8 && (offset & 0x1) == 0 && offset <= 62 {
2497                    // STRH Rd, [Rn, #imm5*2] (16-bit): 1000 0 imm5 Rn Rd
2498                    let imm5 = (offset >> 1) as u16;
2499                    let instr: u16 =
2500                        0x8000 | (imm5 << 6) | ((base_bits as u16) << 3) | (rd_bits as u16);
2501                    Ok(instr.to_le_bytes().to_vec())
2502                } else {
2503                    self.encode_thumb32_strh_imm(rd, &addr.base, offset)
2504                }
2505            }
2506
2507            // MemorySize (Thumb-2)
2508            ArmOp::MemorySize { rd } => {
2509                // LSR rd, R10, #16 — memory size in bytes / 65536 = pages
2510                // Thumb-2 16-bit: LSRS Rd, Rm, #imm5 — 0000 1 imm5 Rm Rd
2511                let rd_bits = reg_to_bits(rd);
2512                let r10_bits = reg_to_bits(&Reg::R10);
2513                if rd_bits < 8 && r10_bits < 8 {
2514                    let instr: u16 =
2515                        0x0800 | (16u16 << 6) | ((r10_bits as u16) << 3) | (rd_bits as u16);
2516                    Ok(instr.to_le_bytes().to_vec())
2517                } else {
2518                    // Thumb-2 32-bit LSR: 1110 1010 010 0 1111 | 0 imm3 Rd imm2 01 Rm
2519                    let imm5: u32 = 16;
2520                    let imm3 = (imm5 >> 2) & 0x7;
2521                    let imm2 = imm5 & 0x3;
2522                    let hw1: u16 = 0xEA4F;
2523                    let hw2: u16 =
2524                        ((imm3 << 12) | (rd_bits << 8) | (imm2 << 6) | 0x10 | r10_bits) as u16;
2525                    let mut bytes = hw1.to_le_bytes().to_vec();
2526                    bytes.extend_from_slice(&hw2.to_le_bytes());
2527                    Ok(bytes)
2528                }
2529            }
2530
2531            // MemoryGrow (Thumb-2)
2532            ArmOp::MemoryGrow { rd, .. } => {
2533                // On embedded with fixed memory, always return -1 (failure)
2534                // MVN rd, #0 → MOV rd, #-1
2535                // Thumb-2 32-bit: MVN: 1111 0 i 0 0 0 1 1 0 1111 | 0 imm3 Rd imm8
2536                let rd_bits = reg_to_bits(rd);
2537                let hw1: u16 = 0xF06F; // MVN with i=0
2538                let hw2: u16 = (rd_bits << 8) as u16; // imm8=0 → ~0 = 0xFFFFFFFF = -1
2539                let mut bytes = hw1.to_le_bytes().to_vec();
2540                bytes.extend_from_slice(&hw2.to_le_bytes());
2541                Ok(bytes)
2542            }
2543
2544            // BX (16-bit)
2545            ArmOp::Bx { rm } => {
2546                let rm_bits = reg_to_bits(rm) as u16;
2547                // BX Rm (16-bit): 0100 0111 0 Rm 000
2548                let instr: u16 = 0x4700 | (rm_bits << 3);
2549                Ok(instr.to_le_bytes().to_vec())
2550            }
2551
2552            // BLX (16-bit) - Branch with Link and Exchange
2553            // BLX Rm: 0100 0111 1 Rm 000
2554            ArmOp::Blx { rm } => {
2555                let rm_bits = reg_to_bits(rm) as u16;
2556                let instr: u16 = 0x4780 | (rm_bits << 3);
2557                Ok(instr.to_le_bytes().to_vec())
2558            }
2559
2560            // CallIndirect - indirect function call via table lookup
2561            // table_index_reg contains the table index
2562            // Generates: LSL R12, idx, #2; LDR R12, [R12, table_base]; BLX R12
2563            ArmOp::CallIndirect {
2564                rd: _,
2565                type_idx: _,
2566                table_index_reg,
2567            } => {
2568                let idx_reg = reg_to_bits(table_index_reg);
2569                let mut bytes = Vec::new();
2570
2571                // For now, we generate code that:
2572                // 1. Multiplies index by 4 (function pointer size)
2573                // 2. Loads function pointer from table (assumes table base in R11)
2574                // 3. Calls the function via BLX
2575                //
2576                // Table base setup must be done by caller/runtime.
2577                // This is a simplified implementation - full support needs:
2578                // - Table base address resolution
2579                // - Type signature checking
2580                // - Bounds checking
2581
2582                // LSL R12, idx_reg, #2 (multiply index by 4)
2583                // Thumb-2 MOV with shift: 11101010 010 S 1111 | 0 imm3 Rd imm2 type Rm
2584                // LSL: type=00, imm5=2 -> imm3=0, imm2=10
2585                let hw1: u16 = 0xEA4F_u16; // MOV.W R12, Rm, LSL #2
2586                let hw2: u16 = ((0x0C00 | (0b10 << 4)) | idx_reg) as u16;
2587                bytes.extend_from_slice(&hw1.to_le_bytes());
2588                bytes.extend_from_slice(&hw2.to_le_bytes());
2589
2590                // LDR R12, [R11, R12] - load function pointer
2591                // Thumb-2 LDR (register): 1111 1000 0101 Rn | Rt 0000 00 imm2 Rm
2592                // Rn=R11, Rt=R12, Rm=R12, imm2=00 (no shift)
2593                let ldr_hw1: u16 = 0xF85B; // LDR.W Rt, [R11, Rm]
2594                let ldr_hw2: u16 = 0xC00C; // Rt=R12, imm2=00, Rm=R12
2595                bytes.extend_from_slice(&ldr_hw1.to_le_bytes());
2596                bytes.extend_from_slice(&ldr_hw2.to_le_bytes());
2597
2598                // BLX R12 (call function indirectly)
2599                // BLX Rm (16-bit): 0100 0111 1 Rm 000
2600                let blx: u16 = 0x47E0; // BLX R12
2601                bytes.extend_from_slice(&blx.to_le_bytes());
2602
2603                Ok(bytes)
2604            }
2605
2606            // Label pseudo-instruction: emits no machine code
2607            ArmOp::Label { .. } => Ok(Vec::new()),
2608
2609            // Conditional branch to label (generic) - offset 0, will be patched
2610            ArmOp::Bcc { cond, label: _ } => {
2611                use synth_synthesis::Condition;
2612                let cond_bits: u16 = match cond {
2613                    Condition::EQ => 0x0,
2614                    Condition::NE => 0x1,
2615                    Condition::HS => 0x2,
2616                    Condition::LO => 0x3,
2617                    Condition::HI => 0x8,
2618                    Condition::LS => 0x9,
2619                    Condition::GE => 0xA,
2620                    Condition::LT => 0xB,
2621                    Condition::GT => 0xC,
2622                    Condition::LE => 0xD,
2623                };
2624                // 16-bit B<cond> with offset 0: 1101 cond imm8
2625                let instr: u16 = 0xD000 | (cond_bits << 8);
2626                Ok(instr.to_le_bytes().to_vec())
2627            }
2628
2629            // Branch instructions
2630            ArmOp::B { label: _ } => {
2631                // Simplified: B.N with offset 0
2632                // For real usage, would need label resolution
2633                let instr: u16 = 0xE000; // B.N #0
2634                Ok(instr.to_le_bytes().to_vec())
2635            }
2636
2637            // BHS (Branch if Higher or Same) - used for bounds checking
2638            // Condition code: 0x2 (C set)
2639            ArmOp::Bhs { label: _ } => {
2640                // 16-bit B<cond> with offset 0: 1101 cond imm8
2641                // cond = 0x2 (HS)
2642                let instr: u16 = 0xD200; // BHS.N #0
2643                Ok(instr.to_le_bytes().to_vec())
2644            }
2645
2646            // BLO (Branch if Lower) - complementary to BHS
2647            // Condition code: 0x3 (C clear)
2648            ArmOp::Blo { label: _ } => {
2649                // 16-bit B<cond> with offset 0: 1101 cond imm8
2650                // cond = 0x3 (LO)
2651                let instr: u16 = 0xD300; // BLO.N #0
2652                Ok(instr.to_le_bytes().to_vec())
2653            }
2654
2655            // Branch with numeric offset (Thumb-2)
2656            // Thumb-2 B.W instruction: 32-bit with +-16MB range
2657            ArmOp::BOffset { offset } => {
2658                // offset is already the halfword displacement: (target - branch - 4) / 2
2659                // This is the raw encoded value, accounting for variable-length instructions
2660                let halfword_offset = *offset;
2661
2662                // 16-bit B.N encoding: 1110 0 imm11 (11-bit signed halfword offset)
2663                // Range: -1024 to +1022 halfwords
2664                if (-1024..=1022).contains(&halfword_offset) {
2665                    // 16-bit B.N encoding: 1110 0 imm11
2666                    let imm11 = (halfword_offset as u16) & 0x7FF;
2667                    let instr: u16 = 0xE000 | imm11;
2668                    Ok(instr.to_le_bytes().to_vec())
2669                } else {
2670                    // 32-bit B.W encoding for larger offsets
2671                    // First halfword: 1111 0 S imm10
2672                    // Second halfword: 10 J1 0 J2 imm11
2673                    // Total offset = SignExtend(S:I1:I2:imm10:imm11:0)
2674                    // where I1 = NOT(J1 XOR S), I2 = NOT(J2 XOR S)
2675
2676                    // The B.W (T4) encoding packs the signed offset as:
2677                    //   S:I1:I2:imm10:imm11:0  (25-bit signed, halfword-aligned)
2678                    // where J1 = NOT(I1 XOR S), J2 = NOT(I2 XOR S)
2679                    // Input halfword_offset already equals (target - PC - 4) / 2,
2680                    // so the full byte offset = halfword_offset << 1.
2681                    // The encoding fields split that 25-bit signed value (including the
2682                    // implicit trailing zero) as: S | imm10 | imm11
2683                    // with I1 = bit 23 and I2 = bit 22 of the signed offset.
2684                    let signed_offset = halfword_offset << 1; // byte offset
2685                    let s = if signed_offset < 0 { 1u32 } else { 0u32 };
2686                    let uoffset = signed_offset as u32;
2687                    let imm10 = (uoffset >> 12) & 0x3FF; // bits [21:12]
2688                    let imm11 = (uoffset >> 1) & 0x7FF; // bits [11:1]
2689                    let i1 = (uoffset >> 23) & 1; // bit 23
2690                    let i2 = (uoffset >> 22) & 1; // bit 22
2691                    let j1 = (!(i1 ^ s)) & 1; // J1 = NOT(I1 XOR S)
2692                    let j2 = (!(i2 ^ s)) & 1; // J2 = NOT(I2 XOR S)
2693
2694                    let hw1: u16 = (0xF000 | (s << 10) | imm10) as u16;
2695                    let hw2: u16 = (0x9000 | (j1 << 13) | (j2 << 11) | imm11) as u16;
2696
2697                    let mut bytes = hw1.to_le_bytes().to_vec();
2698                    bytes.extend_from_slice(&hw2.to_le_bytes());
2699                    Ok(bytes)
2700                }
2701            }
2702
2703            // Conditional branch with numeric offset (Thumb-2)
2704            ArmOp::BCondOffset { cond, offset } => {
2705                use synth_synthesis::Condition;
2706                let cond_bits: u16 = match cond {
2707                    Condition::EQ => 0x0,
2708                    Condition::NE => 0x1,
2709                    Condition::HS => 0x2,
2710                    Condition::LO => 0x3,
2711                    Condition::HI => 0x8,
2712                    Condition::LS => 0x9,
2713                    Condition::GE => 0xA,
2714                    Condition::LT => 0xB,
2715                    Condition::GT => 0xC,
2716                    Condition::LE => 0xD,
2717                };
2718
2719                // offset is already the halfword displacement: (target - branch - 4) / 2
2720                // This is the raw imm8 value for 16-bit B<cond> encoding
2721                let halfword_offset = *offset;
2722
2723                // 16-bit B<cond> encoding: 1101 cond imm8
2724                // Range: -256 to +254 halfwords (imm8 is sign-extended and shifted left 1)
2725                if (-128..=127).contains(&halfword_offset) {
2726                    let imm8 = (halfword_offset as u16) & 0xFF;
2727                    let instr: u16 = 0xD000 | (cond_bits << 8) | imm8;
2728                    Ok(instr.to_le_bytes().to_vec())
2729                } else {
2730                    // 32-bit B<cond>.W for larger offsets
2731                    // First halfword: 1111 0 S cond imm6
2732                    // Second halfword: 10 J1 0 J2 imm11
2733                    let offset = halfword_offset >> 1;
2734                    let s = if offset < 0 { 1u32 } else { 0u32 };
2735                    let imm6 = ((offset >> 11) as u32) & 0x3F;
2736                    let imm11 = (offset as u32) & 0x7FF;
2737                    let j1 = if s == 1 { 1 } else { 0 };
2738                    let j2 = if s == 1 { 1 } else { 0 };
2739
2740                    let hw1: u16 = (0xF000 | (s << 10) | ((cond_bits as u32) << 6) | imm6) as u16;
2741                    let hw2: u16 = (0x8000 | (j1 << 13) | (j2 << 11) | imm11) as u16;
2742
2743                    let mut bytes = hw1.to_le_bytes().to_vec();
2744                    bytes.extend_from_slice(&hw2.to_le_bytes());
2745                    Ok(bytes)
2746                }
2747            }
2748
2749            ArmOp::Bl { label: _ } => {
2750                // BL is always 32-bit in Thumb-2, encoded here as a relocatable
2751                // placeholder; an R_ARM_THM_CALL relocation patches the target
2752                // (see arm_backend.rs). The placeholder must carry an embedded
2753                // addend of -4 so the relocation nets to exactly the symbol S.
2754                //
2755                // Thumb BL computes `target = (P + 4) + signed_offset`. Under
2756                // R_ARM_THM_CALL the linker resolves using the in-place addend;
2757                // a 0xF800 placeholder (addend 0) lands at S+4 — every call one
2758                // instruction past the callee entry (#174). The correct
2759                // placeholder is what `gas` emits for `bl <extern>`:
2760                //   f7ff fffe  ->  `bl <self>`  (S=1, J1=J2=1, imm = -4 addend),
2761                // i.e. hw1=0xF7FF, hw2=0xFFFE. This nets to S, not S+4.
2762                // (The earlier 0xD000 was worse still — a ~+0x600000 addend,
2763                // the garbage `bl c0000c` and "truncated to fit" of #167.)
2764                let hw1: u16 = 0xF7FF;
2765                let hw2: u16 = 0xFFFE;
2766                let mut bytes = hw1.to_le_bytes().to_vec();
2767                bytes.extend_from_slice(&hw2.to_le_bytes());
2768                Ok(bytes)
2769            }
2770
2771            // MVN
2772            ArmOp::Mvn { rd, op2 } => {
2773                if let Operand2::Reg(rm) = op2 {
2774                    let rd_bits = reg_to_bits(rd) as u16;
2775                    let rm_bits = reg_to_bits(rm) as u16;
2776
2777                    if rd_bits < 8 && rm_bits < 8 {
2778                        // MVNS Rd, Rm (16-bit): 0100 0011 11 Rm Rd
2779                        let instr: u16 = 0x43C0 | (rm_bits << 3) | rd_bits;
2780                        Ok(instr.to_le_bytes().to_vec())
2781                    } else {
2782                        // 32-bit MVN
2783                        let hw1: u16 = 0xEA6F_u16;
2784                        let hw2: u16 = ((reg_to_bits(rd) << 8) | reg_to_bits(rm)) as u16;
2785                        let mut bytes = hw1.to_le_bytes().to_vec();
2786                        bytes.extend_from_slice(&hw2.to_le_bytes());
2787                        Ok(bytes)
2788                    }
2789                } else {
2790                    let instr: u16 = 0xBF00;
2791                    Ok(instr.to_le_bytes().to_vec())
2792                }
2793            }
2794
2795            // MOVW - Move Wide (Thumb-2 32-bit)
2796            ArmOp::Movw { rd, imm16 } => {
2797                self.encode_thumb32_movw_raw(reg_to_bits(rd), *imm16 as u32)
2798            }
2799
2800            // MOVT - Move Top (Thumb-2 32-bit)
2801            ArmOp::Movt { rd, imm16 } => {
2802                self.encode_thumb32_movt_raw(reg_to_bits(rd), *imm16 as u32)
2803            }
2804
2805            // #237: symbol-relative MOVW/MOVT. Encode the addend's low/high 16
2806            // bits in place; the backend records an R_ARM_MOVW_ABS_NC /
2807            // R_ARM_MOVT_ABS relocation against `symbol`, so the linker adds the
2808            // symbol's final address to the in-place addend (REL semantics).
2809            ArmOp::MovwSym { rd, addend, .. } => {
2810                self.encode_thumb32_movw_raw(reg_to_bits(rd), (*addend as u32) & 0xffff)
2811            }
2812            ArmOp::MovtSym { rd, addend, .. } => {
2813                self.encode_thumb32_movt_raw(reg_to_bits(rd), ((*addend as u32) >> 16) & 0xffff)
2814            }
2815
2816            // SetCond: Materialize condition flag into register (0 or 1)
2817            // Strategy: ITE <cond>; MOV Rd, #1; MOV Rd, #0
2818            // IMPORTANT: Must use ITE (If-Then-Else) because 16-bit Thumb MOV
2819            // always sets flags (MOVS). We need to evaluate the condition BEFORE
2820            // any MOV instruction clobbers the flags from CMP.
2821            ArmOp::SetCond { rd, cond } => {
2822                let rd_bits = reg_to_bits(rd) as u16;
2823
2824                // Condition code encoding for IT block
2825                use synth_synthesis::Condition;
2826                let cond_bits: u16 = match cond {
2827                    Condition::EQ => 0x0,
2828                    Condition::NE => 0x1,
2829                    Condition::LT => 0xB,
2830                    Condition::LE => 0xD,
2831                    Condition::GT => 0xC,
2832                    Condition::GE => 0xA,
2833                    Condition::LO => 0x3, // CC/LO (unsigned <)
2834                    Condition::LS => 0x9, // LS (unsigned <=)
2835                    Condition::HI => 0x8, // HI (unsigned >)
2836                    Condition::HS => 0x2, // CS/HS (unsigned >=)
2837                };
2838
2839                // ITE <cond>: encodes If-Then-Else block
2840                // The mask field depends on firstcond[0]:
2841                // - If firstcond[0] = 0: mask = 0xC for TE pattern (ITE EQ = BF0C)
2842                // - If firstcond[0] = 1: mask = 0x4 for TE pattern (ITE NE = BF14)
2843                let mask = if (cond_bits & 1) == 0 { 0xC } else { 0x4 };
2844                let ite_instr: u16 = 0xBF00 | (cond_bits << 4) | mask;
2845
2846                // Materialize 0/1 into Rd. The 16-bit MOVS (T1) encodes Rd in a
2847                // 3-bit field (bits[10:8]) — only R0–R7. For a high register
2848                // (R8–R12) `rd_bits << 8` overflows into bit 11 and silently
2849                // turns MOVS into CMP (00100 → 00101), corrupting the result
2850                // (this mis-materialized gale's `has_waiter`, so its `local.set`
2851                // stored a stale register → the binary-sem WAKE dispatch read
2852                // garbage). Use the 32-bit MOV.W (T2) for high registers, which
2853                // has a 4-bit Rd field. MOV.W with S=0 doesn't set flags, which
2854                // is fine inside the ITE (the materialized value is the result;
2855                // the flags are not consumed afterwards).
2856                let mut bytes = ite_instr.to_le_bytes().to_vec();
2857                let push_mov = |bytes: &mut Vec<u8>, imm: u16| {
2858                    if rd_bits <= 7 {
2859                        let m: u16 = 0x2000 | (rd_bits << 8) | imm; // 16-bit MOVS Rd,#imm
2860                        bytes.extend_from_slice(&m.to_le_bytes());
2861                    } else {
2862                        // 32-bit MOV.W Rd, #imm (T2): F04F | (Rd<<8) | imm8
2863                        let hw1: u16 = 0xF04F;
2864                        let hw2: u16 = (rd_bits << 8) | imm;
2865                        bytes.extend_from_slice(&hw1.to_le_bytes());
2866                        bytes.extend_from_slice(&hw2.to_le_bytes());
2867                    }
2868                };
2869                push_mov(&mut bytes, 1); // Then branch (condition true)  → 1
2870                push_mov(&mut bytes, 0); // Else branch (condition false) → 0
2871                Ok(bytes)
2872            }
2873
2874            // I64SetCond: Compare two i64 register pairs, result 0/1 in rd
2875            // EQ/NE: CMP lo,lo; IT EQ; CMPEQ hi,hi; ITE <cond>; MOV 1; MOV 0
2876            // LT: CMP lo,lo; SBCS rd,hi,hi; ITE LT; MOV 1; MOV 0
2877            // GT: CMP lo,lo (swapped); SBCS rd,hi,hi (swapped); ITE LT; MOV 1; MOV 0
2878            ArmOp::I64SetCond {
2879                rd,
2880                rn_lo,
2881                rn_hi,
2882                rm_lo,
2883                rm_hi,
2884                cond,
2885            } => {
2886                use synth_synthesis::Condition;
2887                let rd_bits = reg_to_bits(rd) as u16;
2888                let mut bytes = Vec::new();
2889
2890                // Helper: encode CMP Rn, Rm (16-bit)
2891                let encode_cmp_reg = |rn: &synth_synthesis::Reg,
2892                                      rm: &synth_synthesis::Reg|
2893                 -> Vec<u8> {
2894                    let rn_bits = reg_to_bits(rn) as u16;
2895                    let rm_bits = reg_to_bits(rm) as u16;
2896                    if rn_bits < 8 && rm_bits < 8 {
2897                        let instr: u16 = 0x4280 | (rm_bits << 3) | rn_bits;
2898                        instr.to_le_bytes().to_vec()
2899                    } else {
2900                        let n_bit = (rn_bits >> 3) & 1;
2901                        let instr: u16 = 0x4500 | (n_bit << 7) | (rm_bits << 3) | (rn_bits & 0x7);
2902                        instr.to_le_bytes().to_vec()
2903                    }
2904                };
2905
2906                // Helper: encode ITE <cond> (2 bytes)
2907                let encode_ite = |cond_bits: u16| -> Vec<u8> {
2908                    let mask = if (cond_bits & 1) == 0 { 0xC } else { 0x4 };
2909                    let ite_instr: u16 = 0xBF00 | (cond_bits << 4) | mask;
2910                    ite_instr.to_le_bytes().to_vec()
2911                };
2912
2913                // Helper: encode SetCond (ITE + MOV #1 + MOV #0) for given condition
2914                let encode_setcond = |cond_bits: u16, rd_bits: u16| -> Vec<u8> {
2915                    let mut b = encode_ite(cond_bits);
2916                    if rd_bits < 8 {
2917                        let mov_one: u16 = 0x2001 | (rd_bits << 8);
2918                        let mov_zero: u16 = 0x2000 | (rd_bits << 8);
2919                        b.extend_from_slice(&mov_one.to_le_bytes());
2920                        b.extend_from_slice(&mov_zero.to_le_bytes());
2921                    } else {
2922                        // #311: rd >= R8 — the 16-bit MOV imm8 form has a 3-bit
2923                        // rd field; rd_bits<<8 overflows into bit 11 and
2924                        // TRANSMUTES the MOV into CMP (0x2001|0x0800 = 0x2801 =
2925                        // CMP r0,#1): the boolean dies in the flags and the
2926                        // consumer reads a stale register. Use the 32-bit
2927                        // MOV.W (T2: F04F 0000|rd<<8|imm8) — IT-legal,
2928                        // flag-preserving. Same class as H-CODE-9 / #180.
2929                        for imm in [1u16, 0u16] {
2930                            let hw1: u16 = 0xF04F;
2931                            let hw2: u16 = (rd_bits << 8) | imm;
2932                            b.extend_from_slice(&hw1.to_le_bytes());
2933                            b.extend_from_slice(&hw2.to_le_bytes());
2934                        }
2935                    }
2936                    b
2937                };
2938
2939                match cond {
2940                    Condition::EQ | Condition::NE => {
2941                        // CMP rn_lo, rm_lo (compare low words)
2942                        bytes.extend_from_slice(&encode_cmp_reg(rn_lo, rm_lo));
2943
2944                        // IT EQ (execute next instruction only if Z=1)
2945                        let it_eq: u16 = 0xBF08; // IT EQ: cond=0000, mask=1000
2946                        bytes.extend_from_slice(&it_eq.to_le_bytes());
2947
2948                        // CMPEQ rn_hi, rm_hi (compare high words, only if low equal)
2949                        bytes.extend_from_slice(&encode_cmp_reg(rn_hi, rm_hi));
2950
2951                        // ITE <cond>; MOV rd, #1; MOV rd, #0
2952                        let cond_bits: u16 = match cond {
2953                            Condition::EQ => 0x0,
2954                            Condition::NE => 0x1,
2955                            _ => unreachable!(),
2956                        };
2957                        bytes.extend_from_slice(&encode_setcond(cond_bits, rd_bits));
2958                    }
2959
2960                    Condition::LT => {
2961                        // CMP rn_lo, rm_lo (sets C flag for borrow)
2962                        bytes.extend_from_slice(&encode_cmp_reg(rn_lo, rm_lo));
2963
2964                        // SBCS rd, rn_hi, rm_hi (subtract with carry, sets N,V flags)
2965                        // SBCS.W Rd, Rn, Rm: EB70 Rn | 0000 Rd 0000 Rm
2966                        let rn_hi_bits = reg_to_bits(rn_hi);
2967                        let rm_hi_bits = reg_to_bits(rm_hi);
2968                        let hw1: u16 = (0xEB70 | rn_hi_bits) as u16;
2969                        let hw2: u16 = ((rd_bits as u32) << 8 | rm_hi_bits) as u16;
2970                        bytes.extend_from_slice(&hw1.to_le_bytes());
2971                        bytes.extend_from_slice(&hw2.to_le_bytes());
2972
2973                        // ITE LT; MOV rd, #1; MOV rd, #0
2974                        bytes.extend_from_slice(&encode_setcond(0xB, rd_bits)); // LT = 0xB
2975                    }
2976
2977                    Condition::GT => {
2978                        // GT(a,b) = LT(b,a): swap operands
2979                        // CMP rm_lo, rn_lo (swapped)
2980                        bytes.extend_from_slice(&encode_cmp_reg(rm_lo, rn_lo));
2981
2982                        // SBCS rd, rm_hi, rn_hi (swapped)
2983                        let rm_hi_bits = reg_to_bits(rm_hi);
2984                        let rn_hi_bits = reg_to_bits(rn_hi);
2985                        let hw1: u16 = (0xEB70 | rm_hi_bits) as u16;
2986                        let hw2: u16 = ((rd_bits as u32) << 8 | rn_hi_bits) as u16;
2987                        bytes.extend_from_slice(&hw1.to_le_bytes());
2988                        bytes.extend_from_slice(&hw2.to_le_bytes());
2989
2990                        // ITE LT; MOV rd, #1; MOV rd, #0
2991                        bytes.extend_from_slice(&encode_setcond(0xB, rd_bits)); // LT = 0xB
2992                    }
2993
2994                    Condition::LE => {
2995                        // LE(a,b) = !GT(a,b): use GT logic but invert result
2996                        // GT(a,b) = LT(b,a): so we do CMP(b,a) and check LT, then invert
2997                        // CMP rm_lo, rn_lo (swapped, same as GT)
2998                        bytes.extend_from_slice(&encode_cmp_reg(rm_lo, rn_lo));
2999
3000                        // SBCS rd, rm_hi, rn_hi (swapped)
3001                        let rm_hi_bits = reg_to_bits(rm_hi);
3002                        let rn_hi_bits = reg_to_bits(rn_hi);
3003                        let hw1: u16 = (0xEB70 | rm_hi_bits) as u16;
3004                        let hw2: u16 = ((rd_bits as u32) << 8 | rn_hi_bits) as u16;
3005                        bytes.extend_from_slice(&hw1.to_le_bytes());
3006                        bytes.extend_from_slice(&hw2.to_le_bytes());
3007
3008                        // ITE GE; MOV rd, #1; MOV rd, #0 (GE is !LT, so inverting GT result)
3009                        bytes.extend_from_slice(&encode_setcond(0xA, rd_bits)); // GE = 0xA
3010                    }
3011
3012                    Condition::GE => {
3013                        // GE(a,b) = !LT(a,b): use LT logic but invert result
3014                        // CMP rn_lo, rm_lo (same as LT)
3015                        bytes.extend_from_slice(&encode_cmp_reg(rn_lo, rm_lo));
3016
3017                        // SBCS rd, rn_hi, rm_hi (same as LT)
3018                        let rn_hi_bits = reg_to_bits(rn_hi);
3019                        let rm_hi_bits = reg_to_bits(rm_hi);
3020                        let hw1: u16 = (0xEB70 | rn_hi_bits) as u16;
3021                        let hw2: u16 = ((rd_bits as u32) << 8 | rm_hi_bits) as u16;
3022                        bytes.extend_from_slice(&hw1.to_le_bytes());
3023                        bytes.extend_from_slice(&hw2.to_le_bytes());
3024
3025                        // ITE GE; MOV rd, #1; MOV rd, #0 (GE is !LT)
3026                        bytes.extend_from_slice(&encode_setcond(0xA, rd_bits)); // GE = 0xA
3027                    }
3028
3029                    // Unsigned comparisons - same instruction sequence, different conditions
3030                    Condition::LO => {
3031                        // LO (unsigned LT): CMP lo, SBCS hi, check C=0
3032                        bytes.extend_from_slice(&encode_cmp_reg(rn_lo, rm_lo));
3033                        let rn_hi_bits = reg_to_bits(rn_hi);
3034                        let rm_hi_bits = reg_to_bits(rm_hi);
3035                        let hw1: u16 = (0xEB70 | rn_hi_bits) as u16;
3036                        let hw2: u16 = ((rd_bits as u32) << 8 | rm_hi_bits) as u16;
3037                        bytes.extend_from_slice(&hw1.to_le_bytes());
3038                        bytes.extend_from_slice(&hw2.to_le_bytes());
3039                        bytes.extend_from_slice(&encode_setcond(0x3, rd_bits)); // LO = 0x3 (CC)
3040                    }
3041
3042                    Condition::HI => {
3043                        // HI (unsigned GT): swap operands and check LO
3044                        bytes.extend_from_slice(&encode_cmp_reg(rm_lo, rn_lo));
3045                        let rm_hi_bits = reg_to_bits(rm_hi);
3046                        let rn_hi_bits = reg_to_bits(rn_hi);
3047                        let hw1: u16 = (0xEB70 | rm_hi_bits) as u16;
3048                        let hw2: u16 = ((rd_bits as u32) << 8 | rn_hi_bits) as u16;
3049                        bytes.extend_from_slice(&hw1.to_le_bytes());
3050                        bytes.extend_from_slice(&hw2.to_le_bytes());
3051                        bytes.extend_from_slice(&encode_setcond(0x3, rd_bits)); // LO = 0x3 (CC)
3052                    }
3053
3054                    Condition::LS => {
3055                        // LS (unsigned LE): !(a > b) = !(HI), so do HI and invert
3056                        bytes.extend_from_slice(&encode_cmp_reg(rm_lo, rn_lo));
3057                        let rm_hi_bits = reg_to_bits(rm_hi);
3058                        let rn_hi_bits = reg_to_bits(rn_hi);
3059                        let hw1: u16 = (0xEB70 | rm_hi_bits) as u16;
3060                        let hw2: u16 = ((rd_bits as u32) << 8 | rn_hi_bits) as u16;
3061                        bytes.extend_from_slice(&hw1.to_le_bytes());
3062                        bytes.extend_from_slice(&hw2.to_le_bytes());
3063                        bytes.extend_from_slice(&encode_setcond(0x2, rd_bits)); // HS = 0x2 (CS) = !LO
3064                    }
3065
3066                    Condition::HS => {
3067                        // HS (unsigned GE): !(a < b) = !(LO)
3068                        bytes.extend_from_slice(&encode_cmp_reg(rn_lo, rm_lo));
3069                        let rn_hi_bits = reg_to_bits(rn_hi);
3070                        let rm_hi_bits = reg_to_bits(rm_hi);
3071                        let hw1: u16 = (0xEB70 | rn_hi_bits) as u16;
3072                        let hw2: u16 = ((rd_bits as u32) << 8 | rm_hi_bits) as u16;
3073                        bytes.extend_from_slice(&hw1.to_le_bytes());
3074                        bytes.extend_from_slice(&hw2.to_le_bytes());
3075                        bytes.extend_from_slice(&encode_setcond(0x2, rd_bits)); // HS = 0x2 (CS) = !LO
3076                    }
3077                }
3078
3079                Ok(bytes)
3080            }
3081
3082            // I64SetCondZ: Test if i64 register pair is zero, result 0/1 in rd
3083            // ORR.W rd, rn_lo, rn_hi; CMP rd, #0; ITE EQ; MOV 1; MOV 0
3084            ArmOp::I64SetCondZ { rd, rn_lo, rn_hi } => {
3085                let rd_bits = reg_to_bits(rd);
3086                let rn_lo_bits = reg_to_bits(rn_lo);
3087                let rn_hi_bits = reg_to_bits(rn_hi);
3088                let mut bytes = Vec::new();
3089
3090                // ORR.W rd, rn_lo, rn_hi: EA40 rn_lo | 0000 rd 0000 rn_hi
3091                let hw1: u16 = (0xEA40 | rn_lo_bits) as u16;
3092                let hw2: u16 = ((rd_bits << 8) | rn_hi_bits) as u16;
3093                bytes.extend_from_slice(&hw1.to_le_bytes());
3094                bytes.extend_from_slice(&hw2.to_le_bytes());
3095
3096                // CMP rd, #0 — 16-bit form only for r0-r7 (3-bit rd field);
3097                // high registers take CMP.W (T2: F1B0|rn 0F00|imm8). This was
3098                // H-CODE-9: rd_bits<<8 overflowing the field compared the
3099                // WRONG register. Same hardening as the #311 SetCond fix.
3100                if rd_bits < 8 {
3101                    let cmp_instr: u16 = 0x2800 | ((rd_bits as u16) << 8);
3102                    bytes.extend_from_slice(&cmp_instr.to_le_bytes());
3103                } else {
3104                    let hw1: u16 = 0xF1B0 | (rd_bits as u16);
3105                    let hw2: u16 = 0x0F00;
3106                    bytes.extend_from_slice(&hw1.to_le_bytes());
3107                    bytes.extend_from_slice(&hw2.to_le_bytes());
3108                }
3109
3110                // ITE EQ; MOV rd, #1; MOV rd, #0 (32-bit MOV.W for rd >= R8,
3111                // #311 — see I64SetCond)
3112                let mask = 0xC_u16; // ITE EQ mask: firstcond[0]=0, mask=0xC
3113                let ite_instr: u16 = 0xBF00 | mask;
3114                bytes.extend_from_slice(&ite_instr.to_le_bytes());
3115                if rd_bits < 8 {
3116                    let mov_one: u16 = 0x2001 | ((rd_bits as u16) << 8);
3117                    let mov_zero: u16 = 0x2000 | ((rd_bits as u16) << 8);
3118                    bytes.extend_from_slice(&mov_one.to_le_bytes());
3119                    bytes.extend_from_slice(&mov_zero.to_le_bytes());
3120                } else {
3121                    for imm in [1u16, 0u16] {
3122                        let hw1: u16 = 0xF04F;
3123                        let hw2: u16 = ((rd_bits as u16) << 8) | imm;
3124                        bytes.extend_from_slice(&hw1.to_le_bytes());
3125                        bytes.extend_from_slice(&hw2.to_le_bytes());
3126                    }
3127                }
3128
3129                Ok(bytes)
3130            }
3131
3132            // I64Mul: 64-bit multiply using UMULL + MLA cross products
3133            // Formula: result = (a_lo * b_lo) + ((a_lo * b_hi + a_hi * b_lo) << 32)
3134            // Uses R12 as scratch register
3135            ArmOp::I64Mul {
3136                rd_lo,
3137                rd_hi,
3138                rn_lo,
3139                rn_hi,
3140                rm_lo,
3141                rm_hi,
3142            } => {
3143                let rd_lo_bits = reg_to_bits(rd_lo);
3144                let rd_hi_bits = reg_to_bits(rd_hi);
3145                let rn_lo_bits = reg_to_bits(rn_lo);
3146                let rn_hi_bits = reg_to_bits(rn_hi);
3147                let rm_lo_bits = reg_to_bits(rm_lo);
3148                let rm_hi_bits = reg_to_bits(rm_hi);
3149                let r12: u32 = 12; // IP scratch register
3150                let mut bytes = Vec::new();
3151
3152                // 1. MUL R12, rn_lo, rm_hi  (R12 = a_lo * b_hi)
3153                // Thumb-2 MUL: hw1=0xFB00|Rn, hw2=0xF000|(Rd<<8)|Rm
3154                let hw1: u16 = (0xFB00 | rn_lo_bits) as u16;
3155                let hw2: u16 = (0xF000 | (r12 << 8) | rm_hi_bits) as u16;
3156                bytes.extend_from_slice(&hw1.to_le_bytes());
3157                bytes.extend_from_slice(&hw2.to_le_bytes());
3158
3159                // 2. MLA R12, rn_hi, rm_lo, R12  (R12 += a_hi * b_lo)
3160                // Thumb-2 MLA: hw1=0xFB00|Rn, hw2=(Ra<<12)|(Rd<<8)|Rm
3161                let hw1: u16 = (0xFB00 | rn_hi_bits) as u16;
3162                let hw2: u16 = ((r12 << 12) | (r12 << 8) | rm_lo_bits) as u16;
3163                bytes.extend_from_slice(&hw1.to_le_bytes());
3164                bytes.extend_from_slice(&hw2.to_le_bytes());
3165
3166                // 3. UMULL rd_lo, rd_hi, rn_lo, rm_lo  (rd_lo:rd_hi = a_lo * b_lo)
3167                // Thumb-2 UMULL: hw1=0xFBA0|Rn, hw2=(RdLo<<12)|(RdHi<<8)|Rm
3168                let hw1: u16 = (0xFBA0 | rn_lo_bits) as u16;
3169                let hw2: u16 = ((rd_lo_bits << 12) | (rd_hi_bits << 8) | rm_lo_bits) as u16;
3170                bytes.extend_from_slice(&hw1.to_le_bytes());
3171                bytes.extend_from_slice(&hw2.to_le_bytes());
3172
3173                // 4. ADD rd_hi, R12  (rd_hi += cross products)
3174                // 16-bit high reg ADD: 01000100 D Rm Rdn[2:0]
3175                let d_bit = (rd_hi_bits >> 3) & 1;
3176                let add_instr: u16 =
3177                    (0x4400 | (d_bit << 7) | (r12 << 3) | (rd_hi_bits & 0x7)) as u16;
3178                bytes.extend_from_slice(&add_instr.to_le_bytes());
3179
3180                Ok(bytes)
3181            }
3182
3183            // I64Shl: 64-bit shift left with branch for n<32 vs n>=32
3184            // rm_hi (R3) is used as temp register
3185            ArmOp::I64Shl {
3186                rd_lo,
3187                rd_hi,
3188                rn_lo,
3189                rn_hi,
3190                rm_lo,
3191                rm_hi,
3192            } => {
3193                let rd_lo_bits = reg_to_bits(rd_lo);
3194                let rd_hi_bits = reg_to_bits(rd_hi);
3195                let rn_lo_bits = reg_to_bits(rn_lo);
3196                let rn_hi_bits = reg_to_bits(rn_hi);
3197                let rm_lo_bits = reg_to_bits(rm_lo);
3198                let rm_hi_bits = reg_to_bits(rm_hi); // temp
3199                let mut bytes = Vec::new();
3200
3201                // AND.W rm_lo, rm_lo, #63  (mask shift amount to 6 bits)
3202                let hw1: u16 = (0xF000 | rm_lo_bits) as u16;
3203                let hw2: u16 = ((rm_lo_bits << 8) | 0x3F) as u16;
3204                bytes.extend_from_slice(&hw1.to_le_bytes());
3205                bytes.extend_from_slice(&hw2.to_le_bytes());
3206
3207                // SUBS.W rm_hi, rm_lo, #32  (rm_hi = n-32, sets flags)
3208                let hw1: u16 = (0xF1B0 | rm_lo_bits) as u16;
3209                let hw2: u16 = ((rm_hi_bits << 8) | 0x20) as u16;
3210                bytes.extend_from_slice(&hw1.to_le_bytes());
3211                bytes.extend_from_slice(&hw2.to_le_bytes());
3212
3213                // BPL .large (branch if n >= 32, offset = +10 halfwords)
3214                let bpl: u16 = 0xD50A;
3215                bytes.extend_from_slice(&bpl.to_le_bytes());
3216
3217                // --- Small shift (n < 32) ---
3218                // RSB.W rm_hi, rm_lo, #32  (rm_hi = 32-n)
3219                let hw1: u16 = (0xF1C0 | rm_lo_bits) as u16;
3220                let hw2: u16 = ((rm_hi_bits << 8) | 0x20) as u16;
3221                bytes.extend_from_slice(&hw1.to_le_bytes());
3222                bytes.extend_from_slice(&hw2.to_le_bytes());
3223
3224                // LSR.W rm_hi, rn_lo, rm_hi  (rm_hi = lo >> (32-n), overflow bits)
3225                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3226                let hw2: u16 = (0xF000 | (rm_hi_bits << 8) | rm_hi_bits) as u16;
3227                bytes.extend_from_slice(&hw1.to_le_bytes());
3228                bytes.extend_from_slice(&hw2.to_le_bytes());
3229
3230                // LSL.W rd_hi, rn_hi, rm_lo  (hi <<= n)
3231                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3232                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | rm_lo_bits) as u16;
3233                bytes.extend_from_slice(&hw1.to_le_bytes());
3234                bytes.extend_from_slice(&hw2.to_le_bytes());
3235
3236                // ORR.W rd_hi, rd_hi, rm_hi  (hi |= overflow bits from lo)
3237                let hw1: u16 = (0xEA40 | rd_hi_bits) as u16;
3238                let hw2: u16 = ((rd_hi_bits << 8) | rm_hi_bits) as u16;
3239                bytes.extend_from_slice(&hw1.to_le_bytes());
3240                bytes.extend_from_slice(&hw2.to_le_bytes());
3241
3242                // LSL.W rd_lo, rn_lo, rm_lo  (lo <<= n)
3243                let hw1: u16 = (0xFA00 | rn_lo_bits) as u16;
3244                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | rm_lo_bits) as u16;
3245                bytes.extend_from_slice(&hw1.to_le_bytes());
3246                bytes.extend_from_slice(&hw2.to_le_bytes());
3247
3248                // B .done (skip large shift: +2 halfwords)
3249                let b_done: u16 = 0xE002;
3250                bytes.extend_from_slice(&b_done.to_le_bytes());
3251
3252                // --- Large shift (n >= 32) ---
3253                // LSL.W rd_hi, rn_lo, rm_hi  (hi = lo << (n-32))
3254                let hw1: u16 = (0xFA00 | rn_lo_bits) as u16;
3255                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | rm_hi_bits) as u16;
3256                bytes.extend_from_slice(&hw1.to_le_bytes());
3257                bytes.extend_from_slice(&hw2.to_le_bytes());
3258
3259                // MOV rd_lo, #0
3260                let mov_zero: u16 = 0x2000 | ((rd_lo_bits as u16) << 8);
3261                bytes.extend_from_slice(&mov_zero.to_le_bytes());
3262
3263                Ok(bytes) // Total: 38 bytes
3264            }
3265
3266            // I64ShrU: 64-bit logical shift right with branch for n<32 vs n>=32
3267            ArmOp::I64ShrU {
3268                rd_lo,
3269                rd_hi,
3270                rn_lo,
3271                rn_hi,
3272                rm_lo,
3273                rm_hi,
3274            } => {
3275                let rd_lo_bits = reg_to_bits(rd_lo);
3276                let rd_hi_bits = reg_to_bits(rd_hi);
3277                let rn_lo_bits = reg_to_bits(rn_lo);
3278                let rn_hi_bits = reg_to_bits(rn_hi);
3279                let rm_lo_bits = reg_to_bits(rm_lo);
3280                let rm_hi_bits = reg_to_bits(rm_hi); // temp
3281                let mut bytes = Vec::new();
3282
3283                // AND.W rm_lo, rm_lo, #63
3284                let hw1: u16 = (0xF000 | rm_lo_bits) as u16;
3285                let hw2: u16 = ((rm_lo_bits << 8) | 0x3F) as u16;
3286                bytes.extend_from_slice(&hw1.to_le_bytes());
3287                bytes.extend_from_slice(&hw2.to_le_bytes());
3288
3289                // SUBS.W rm_hi, rm_lo, #32
3290                let hw1: u16 = (0xF1B0 | rm_lo_bits) as u16;
3291                let hw2: u16 = ((rm_hi_bits << 8) | 0x20) as u16;
3292                bytes.extend_from_slice(&hw1.to_le_bytes());
3293                bytes.extend_from_slice(&hw2.to_le_bytes());
3294
3295                // BPL .large (+10 halfwords)
3296                let bpl: u16 = 0xD50A;
3297                bytes.extend_from_slice(&bpl.to_le_bytes());
3298
3299                // --- Small shift (n < 32) ---
3300                // RSB.W rm_hi, rm_lo, #32  (rm_hi = 32-n)
3301                let hw1: u16 = (0xF1C0 | rm_lo_bits) as u16;
3302                let hw2: u16 = ((rm_hi_bits << 8) | 0x20) as u16;
3303                bytes.extend_from_slice(&hw1.to_le_bytes());
3304                bytes.extend_from_slice(&hw2.to_le_bytes());
3305
3306                // LSL.W rm_hi, rn_hi, rm_hi  (rm_hi = hi << (32-n), bits flowing to lo)
3307                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3308                let hw2: u16 = (0xF000 | (rm_hi_bits << 8) | rm_hi_bits) as u16;
3309                bytes.extend_from_slice(&hw1.to_le_bytes());
3310                bytes.extend_from_slice(&hw2.to_le_bytes());
3311
3312                // LSR.W rd_lo, rn_lo, rm_lo  (lo >>= n)
3313                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3314                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | rm_lo_bits) as u16;
3315                bytes.extend_from_slice(&hw1.to_le_bytes());
3316                bytes.extend_from_slice(&hw2.to_le_bytes());
3317
3318                // ORR.W rd_lo, rd_lo, rm_hi  (lo |= overflow from hi)
3319                let hw1: u16 = (0xEA40 | rd_lo_bits) as u16;
3320                let hw2: u16 = ((rd_lo_bits << 8) | rm_hi_bits) as u16;
3321                bytes.extend_from_slice(&hw1.to_le_bytes());
3322                bytes.extend_from_slice(&hw2.to_le_bytes());
3323
3324                // LSR.W rd_hi, rn_hi, rm_lo  (hi >>= n, logical)
3325                let hw1: u16 = (0xFA20 | rn_hi_bits) as u16;
3326                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | rm_lo_bits) as u16;
3327                bytes.extend_from_slice(&hw1.to_le_bytes());
3328                bytes.extend_from_slice(&hw2.to_le_bytes());
3329
3330                // B .done (+2 halfwords)
3331                let b_done: u16 = 0xE002;
3332                bytes.extend_from_slice(&b_done.to_le_bytes());
3333
3334                // --- Large shift (n >= 32) ---
3335                // LSR.W rd_lo, rn_hi, rm_hi  (lo = hi >> (n-32))
3336                let hw1: u16 = (0xFA20 | rn_hi_bits) as u16;
3337                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | rm_hi_bits) as u16;
3338                bytes.extend_from_slice(&hw1.to_le_bytes());
3339                bytes.extend_from_slice(&hw2.to_le_bytes());
3340
3341                // MOV rd_hi, #0
3342                let mov_zero: u16 = 0x2000 | ((rd_hi_bits as u16) << 8);
3343                bytes.extend_from_slice(&mov_zero.to_le_bytes());
3344
3345                Ok(bytes) // Total: 38 bytes
3346            }
3347
3348            // I64ShrS: 64-bit arithmetic shift right with branch for n<32 vs n>=32
3349            ArmOp::I64ShrS {
3350                rd_lo,
3351                rd_hi,
3352                rn_lo,
3353                rn_hi,
3354                rm_lo,
3355                rm_hi,
3356            } => {
3357                let rd_lo_bits = reg_to_bits(rd_lo);
3358                let rd_hi_bits = reg_to_bits(rd_hi);
3359                let rn_lo_bits = reg_to_bits(rn_lo);
3360                let rn_hi_bits = reg_to_bits(rn_hi);
3361                let rm_lo_bits = reg_to_bits(rm_lo);
3362                let rm_hi_bits = reg_to_bits(rm_hi); // temp
3363                let mut bytes = Vec::new();
3364
3365                // AND.W rm_lo, rm_lo, #63
3366                let hw1: u16 = (0xF000 | rm_lo_bits) as u16;
3367                let hw2: u16 = ((rm_lo_bits << 8) | 0x3F) as u16;
3368                bytes.extend_from_slice(&hw1.to_le_bytes());
3369                bytes.extend_from_slice(&hw2.to_le_bytes());
3370
3371                // SUBS.W rm_hi, rm_lo, #32
3372                let hw1: u16 = (0xF1B0 | rm_lo_bits) as u16;
3373                let hw2: u16 = ((rm_hi_bits << 8) | 0x20) as u16;
3374                bytes.extend_from_slice(&hw1.to_le_bytes());
3375                bytes.extend_from_slice(&hw2.to_le_bytes());
3376
3377                // BPL .large (+10 halfwords)
3378                let bpl: u16 = 0xD50A;
3379                bytes.extend_from_slice(&bpl.to_le_bytes());
3380
3381                // --- Small shift (n < 32) ---
3382                // RSB.W rm_hi, rm_lo, #32
3383                let hw1: u16 = (0xF1C0 | rm_lo_bits) as u16;
3384                let hw2: u16 = ((rm_hi_bits << 8) | 0x20) as u16;
3385                bytes.extend_from_slice(&hw1.to_le_bytes());
3386                bytes.extend_from_slice(&hw2.to_le_bytes());
3387
3388                // LSL.W rm_hi, rn_hi, rm_hi  (rm_hi = hi << (32-n), bits flowing to lo)
3389                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3390                let hw2: u16 = (0xF000 | (rm_hi_bits << 8) | rm_hi_bits) as u16;
3391                bytes.extend_from_slice(&hw1.to_le_bytes());
3392                bytes.extend_from_slice(&hw2.to_le_bytes());
3393
3394                // LSR.W rd_lo, rn_lo, rm_lo  (lo >>= n, logical for lo word)
3395                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3396                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | rm_lo_bits) as u16;
3397                bytes.extend_from_slice(&hw1.to_le_bytes());
3398                bytes.extend_from_slice(&hw2.to_le_bytes());
3399
3400                // ORR.W rd_lo, rd_lo, rm_hi  (lo |= overflow from hi)
3401                let hw1: u16 = (0xEA40 | rd_lo_bits) as u16;
3402                let hw2: u16 = ((rd_lo_bits << 8) | rm_hi_bits) as u16;
3403                bytes.extend_from_slice(&hw1.to_le_bytes());
3404                bytes.extend_from_slice(&hw2.to_le_bytes());
3405
3406                // ASR.W rd_hi, rn_hi, rm_lo  (hi >>= n, arithmetic/sign-extending)
3407                let hw1: u16 = (0xFA40 | rn_hi_bits) as u16;
3408                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | rm_lo_bits) as u16;
3409                bytes.extend_from_slice(&hw1.to_le_bytes());
3410                bytes.extend_from_slice(&hw2.to_le_bytes());
3411
3412                // B .done (+3 halfwords, large shift is 8 bytes)
3413                let b_done: u16 = 0xE003;
3414                bytes.extend_from_slice(&b_done.to_le_bytes());
3415
3416                // --- Large shift (n >= 32) ---
3417                // ASR.W rd_lo, rn_hi, rm_hi  (lo = hi >>> (n-32))
3418                let hw1: u16 = (0xFA40 | rn_hi_bits) as u16;
3419                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | rm_hi_bits) as u16;
3420                bytes.extend_from_slice(&hw1.to_le_bytes());
3421                bytes.extend_from_slice(&hw2.to_le_bytes());
3422
3423                // ASR.W rd_hi, rn_hi, #31  (hi = sign extension, all 0s or all 1s)
3424                // Thumb-2 ASR immediate: hw1=0xEA4F, hw2=imm3:Rd:imm2:10:Rm
3425                // imm5=31=11111 → imm3=111, imm2=11
3426                let hw1: u16 = 0xEA4F;
3427                let hw2: u16 = (0x7000 | (rd_hi_bits << 8) | 0x00E0 | rn_hi_bits) as u16;
3428                bytes.extend_from_slice(&hw1.to_le_bytes());
3429                bytes.extend_from_slice(&hw2.to_le_bytes());
3430
3431                Ok(bytes) // Total: 40 bytes
3432            }
3433
3434            // I64Rotl: 64-bit rotate left
3435            // For n < 32: new_hi = (hi << n) | (lo >> (32-n)), new_lo = (lo << n) | (hi >> (32-n))
3436            // For n >= 32: same formula but with lo/hi conceptually swapped, shift by (n-32)
3437            // Uses R4 (saved/restored) and R12 as scratch
3438            ArmOp::I64Rotl {
3439                rdlo,
3440                rdhi,
3441                rnlo,
3442                rnhi,
3443                shift,
3444            } => {
3445                let rd_lo_bits = reg_to_bits(rdlo);
3446                let rd_hi_bits = reg_to_bits(rdhi);
3447                let rn_lo_bits = reg_to_bits(rnlo);
3448                let rn_hi_bits = reg_to_bits(rnhi);
3449                let shift_bits = reg_to_bits(shift);
3450                let r12: u32 = 12; // IP scratch
3451                let r3: u32 = 3; // Scratch (high word of shift amount, unused)
3452                let r4: u32 = 4; // Scratch (saved/restored)
3453                let mut bytes = Vec::new();
3454
3455                // PUSH {R4}
3456                bytes.extend_from_slice(&0xB410u16.to_le_bytes());
3457
3458                // AND.W shift, shift, #63 (mask to 6 bits)
3459                let hw1: u16 = (0xF000 | shift_bits) as u16;
3460                let hw2: u16 = ((shift_bits << 8) | 0x3F) as u16;
3461                bytes.extend_from_slice(&hw1.to_le_bytes());
3462                bytes.extend_from_slice(&hw2.to_le_bytes());
3463
3464                // SUBS.W R3, shift, #32 (R3 = n-32, sets flags)
3465                let hw1: u16 = (0xF1B0 | shift_bits) as u16;
3466                let hw2: u16 = ((r3 << 8) | 0x20) as u16;
3467                bytes.extend_from_slice(&hw1.to_le_bytes());
3468                bytes.extend_from_slice(&hw2.to_le_bytes());
3469
3470                // BPL .large (branch if n >= 32, offset = +14 halfwords)
3471                let bpl: u16 = 0xD50E;
3472                bytes.extend_from_slice(&bpl.to_le_bytes());
3473
3474                // === Small rotation (n < 32) ===
3475                // RSB.W R3, shift, #32 (R3 = 32-n)
3476                let hw1: u16 = (0xF1C0 | shift_bits) as u16;
3477                let hw2: u16 = ((r3 << 8) | 0x20) as u16;
3478                bytes.extend_from_slice(&hw1.to_le_bytes());
3479                bytes.extend_from_slice(&hw2.to_le_bytes());
3480
3481                // LSR.W R4, rn_lo, R3 (R4 = lo >> (32-n), will go to new_hi)
3482                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3483                let hw2: u16 = (0xF000 | (r4 << 8) | r3) as u16;
3484                bytes.extend_from_slice(&hw1.to_le_bytes());
3485                bytes.extend_from_slice(&hw2.to_le_bytes());
3486
3487                // LSR.W R12, rn_hi, R3 (R12 = hi >> (32-n), will go to new_lo)
3488                let hw1: u16 = (0xFA20 | rn_hi_bits) as u16;
3489                let hw2: u16 = (0xF000 | (r12 << 8) | r3) as u16;
3490                bytes.extend_from_slice(&hw1.to_le_bytes());
3491                bytes.extend_from_slice(&hw2.to_le_bytes());
3492
3493                // LSL.W rd_hi, rn_hi, shift (rd_hi = hi << n)
3494                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3495                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | shift_bits) as u16;
3496                bytes.extend_from_slice(&hw1.to_le_bytes());
3497                bytes.extend_from_slice(&hw2.to_le_bytes());
3498
3499                // ORR.W rd_hi, rd_hi, R4 (rd_hi = (hi << n) | (lo >> (32-n)))
3500                let hw1: u16 = (0xEA40 | rd_hi_bits) as u16;
3501                let hw2: u16 = ((rd_hi_bits << 8) | r4) as u16;
3502                bytes.extend_from_slice(&hw1.to_le_bytes());
3503                bytes.extend_from_slice(&hw2.to_le_bytes());
3504
3505                // LSL.W rd_lo, rn_lo, shift (rd_lo = lo << n)
3506                let hw1: u16 = (0xFA00 | rn_lo_bits) as u16;
3507                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | shift_bits) as u16;
3508                bytes.extend_from_slice(&hw1.to_le_bytes());
3509                bytes.extend_from_slice(&hw2.to_le_bytes());
3510
3511                // ORR.W rd_lo, rd_lo, R12 (rd_lo = (lo << n) | (hi >> (32-n)))
3512                let hw1: u16 = (0xEA40 | rd_lo_bits) as u16;
3513                let hw2: u16 = ((rd_lo_bits << 8) | r12) as u16;
3514                bytes.extend_from_slice(&hw1.to_le_bytes());
3515                bytes.extend_from_slice(&hw2.to_le_bytes());
3516
3517                // B .done (skip large block, offset = +14 halfwords)
3518                let b_done: u16 = 0xE00E;
3519                bytes.extend_from_slice(&b_done.to_le_bytes());
3520
3521                // === Large rotation (n >= 32) ===
3522                // R3 already has n-32 from the SUBS
3523                // RSB.W R4, R3, #32 (R4 = 32-(n-32) = 64-n)
3524                let hw1: u16 = (0xF1C0 | r3) as u16;
3525                let hw2: u16 = ((r4 << 8) | 0x20) as u16;
3526                bytes.extend_from_slice(&hw1.to_le_bytes());
3527                bytes.extend_from_slice(&hw2.to_le_bytes());
3528
3529                // LSR.W R12, rn_hi, R4 (R12 = hi >> (64-n), goes to new_hi low bits)
3530                let hw1: u16 = (0xFA20 | rn_hi_bits) as u16;
3531                let hw2: u16 = (0xF000 | (r12 << 8) | r4) as u16;
3532                bytes.extend_from_slice(&hw1.to_le_bytes());
3533                bytes.extend_from_slice(&hw2.to_le_bytes());
3534
3535                // LSR.W R4, rn_lo, R4 (R4 = lo >> (64-n), goes to new_lo low bits)
3536                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3537                let hw2: u16 = (0xF000 | (r4 << 8) | r4) as u16;
3538                bytes.extend_from_slice(&hw1.to_le_bytes());
3539                bytes.extend_from_slice(&hw2.to_le_bytes());
3540
3541                // LSL.W shift, rn_lo, R3 (shift = lo << (n-32), new_hi high bits)
3542                let hw1: u16 = (0xFA00 | rn_lo_bits) as u16;
3543                let hw2: u16 = (0xF000 | (shift_bits << 8) | r3) as u16;
3544                bytes.extend_from_slice(&hw1.to_le_bytes());
3545                bytes.extend_from_slice(&hw2.to_le_bytes());
3546
3547                // ORR.W shift, shift, R12 (shift = (lo << (n-32)) | (hi >> (64-n)) = new_hi)
3548                let hw1: u16 = (0xEA40 | shift_bits) as u16;
3549                let hw2: u16 = ((shift_bits << 8) | r12) as u16;
3550                bytes.extend_from_slice(&hw1.to_le_bytes());
3551                bytes.extend_from_slice(&hw2.to_le_bytes());
3552
3553                // LSL.W rd_lo, rn_hi, R3 (rd_lo = hi << (n-32), new_lo high bits)
3554                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3555                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | r3) as u16;
3556                bytes.extend_from_slice(&hw1.to_le_bytes());
3557                bytes.extend_from_slice(&hw2.to_le_bytes());
3558
3559                // ORR.W rd_lo, rd_lo, R4 (rd_lo = (hi << (n-32)) | (lo >> (64-n)) = new_lo)
3560                let hw1: u16 = (0xEA40 | rd_lo_bits) as u16;
3561                let hw2: u16 = ((rd_lo_bits << 8) | r4) as u16;
3562                bytes.extend_from_slice(&hw1.to_le_bytes());
3563                bytes.extend_from_slice(&hw2.to_le_bytes());
3564
3565                // MOV rd_hi, shift (rd_hi = new_hi)
3566                let d_bit = (rd_hi_bits >> 3) & 1;
3567                let mov_instr: u16 =
3568                    (0x4600 | (d_bit << 7) | (shift_bits << 3) | (rd_hi_bits & 0x7)) as u16;
3569                bytes.extend_from_slice(&mov_instr.to_le_bytes());
3570
3571                // POP {R4}
3572                bytes.extend_from_slice(&0xBC10u16.to_le_bytes());
3573
3574                Ok(bytes) // Total: 74 bytes
3575            }
3576
3577            // I64Rotr: 64-bit rotate right
3578            // rotr(x, n) = rotl(x, 64-n)
3579            // For n < 32: new_lo = (lo >> n) | (hi << (32-n)), new_hi = (hi >> n) | (lo << (32-n))
3580            // For n >= 32: same formula but with lo/hi swapped, shift by (n-32)
3581            ArmOp::I64Rotr {
3582                rdlo,
3583                rdhi,
3584                rnlo,
3585                rnhi,
3586                shift,
3587            } => {
3588                let rd_lo_bits = reg_to_bits(rdlo);
3589                let rd_hi_bits = reg_to_bits(rdhi);
3590                let rn_lo_bits = reg_to_bits(rnlo);
3591                let rn_hi_bits = reg_to_bits(rnhi);
3592                let shift_bits = reg_to_bits(shift);
3593                let r12: u32 = 12;
3594                let r3: u32 = 3;
3595                let r4: u32 = 4;
3596                let mut bytes = Vec::new();
3597
3598                // PUSH {R4}
3599                bytes.extend_from_slice(&0xB410u16.to_le_bytes());
3600
3601                // AND.W shift, shift, #63
3602                let hw1: u16 = (0xF000 | shift_bits) as u16;
3603                let hw2: u16 = ((shift_bits << 8) | 0x3F) as u16;
3604                bytes.extend_from_slice(&hw1.to_le_bytes());
3605                bytes.extend_from_slice(&hw2.to_le_bytes());
3606
3607                // SUBS.W R3, shift, #32
3608                let hw1: u16 = (0xF1B0 | shift_bits) as u16;
3609                let hw2: u16 = ((r3 << 8) | 0x20) as u16;
3610                bytes.extend_from_slice(&hw1.to_le_bytes());
3611                bytes.extend_from_slice(&hw2.to_le_bytes());
3612
3613                // BPL .large (+14 halfwords)
3614                let bpl: u16 = 0xD50E;
3615                bytes.extend_from_slice(&bpl.to_le_bytes());
3616
3617                // === Small rotation (n < 32) ===
3618                // RSB.W R3, shift, #32 (R3 = 32-n)
3619                let hw1: u16 = (0xF1C0 | shift_bits) as u16;
3620                let hw2: u16 = ((r3 << 8) | 0x20) as u16;
3621                bytes.extend_from_slice(&hw1.to_le_bytes());
3622                bytes.extend_from_slice(&hw2.to_le_bytes());
3623
3624                // LSL.W R4, rn_hi, R3 (R4 = hi << (32-n), will go to new_lo)
3625                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3626                let hw2: u16 = (0xF000 | (r4 << 8) | r3) as u16;
3627                bytes.extend_from_slice(&hw1.to_le_bytes());
3628                bytes.extend_from_slice(&hw2.to_le_bytes());
3629
3630                // LSL.W R12, rn_lo, R3 (R12 = lo << (32-n), will go to new_hi)
3631                let hw1: u16 = (0xFA00 | rn_lo_bits) as u16;
3632                let hw2: u16 = (0xF000 | (r12 << 8) | r3) as u16;
3633                bytes.extend_from_slice(&hw1.to_le_bytes());
3634                bytes.extend_from_slice(&hw2.to_le_bytes());
3635
3636                // LSR.W rd_lo, rn_lo, shift (rd_lo = lo >> n)
3637                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3638                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | shift_bits) as u16;
3639                bytes.extend_from_slice(&hw1.to_le_bytes());
3640                bytes.extend_from_slice(&hw2.to_le_bytes());
3641
3642                // ORR.W rd_lo, rd_lo, R4 (rd_lo = (lo >> n) | (hi << (32-n)))
3643                let hw1: u16 = (0xEA40 | rd_lo_bits) as u16;
3644                let hw2: u16 = ((rd_lo_bits << 8) | r4) as u16;
3645                bytes.extend_from_slice(&hw1.to_le_bytes());
3646                bytes.extend_from_slice(&hw2.to_le_bytes());
3647
3648                // LSR.W rd_hi, rn_hi, shift (rd_hi = hi >> n)
3649                let hw1: u16 = (0xFA20 | rn_hi_bits) as u16;
3650                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | shift_bits) as u16;
3651                bytes.extend_from_slice(&hw1.to_le_bytes());
3652                bytes.extend_from_slice(&hw2.to_le_bytes());
3653
3654                // ORR.W rd_hi, rd_hi, R12 (rd_hi = (hi >> n) | (lo << (32-n)))
3655                let hw1: u16 = (0xEA40 | rd_hi_bits) as u16;
3656                let hw2: u16 = ((rd_hi_bits << 8) | r12) as u16;
3657                bytes.extend_from_slice(&hw1.to_le_bytes());
3658                bytes.extend_from_slice(&hw2.to_le_bytes());
3659
3660                // B .done (+14 halfwords)
3661                let b_done: u16 = 0xE00E;
3662                bytes.extend_from_slice(&b_done.to_le_bytes());
3663
3664                // === Large rotation (n >= 32) ===
3665                // RSB.W R4, R3, #32 (R4 = 64-n)
3666                let hw1: u16 = (0xF1C0 | r3) as u16;
3667                let hw2: u16 = ((r4 << 8) | 0x20) as u16;
3668                bytes.extend_from_slice(&hw1.to_le_bytes());
3669                bytes.extend_from_slice(&hw2.to_le_bytes());
3670
3671                // LSL.W R12, rn_lo, R4 (R12 = lo << (64-n), goes to new_lo low bits)
3672                let hw1: u16 = (0xFA00 | rn_lo_bits) as u16;
3673                let hw2: u16 = (0xF000 | (r12 << 8) | r4) as u16;
3674                bytes.extend_from_slice(&hw1.to_le_bytes());
3675                bytes.extend_from_slice(&hw2.to_le_bytes());
3676
3677                // LSL.W R4, rn_hi, R4 (R4 = hi << (64-n), goes to new_hi low bits)
3678                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3679                let hw2: u16 = (0xF000 | (r4 << 8) | r4) as u16;
3680                bytes.extend_from_slice(&hw1.to_le_bytes());
3681                bytes.extend_from_slice(&hw2.to_le_bytes());
3682
3683                // LSR.W shift, rn_hi, R3 (shift = hi >> (n-32), new_lo high bits)
3684                let hw1: u16 = (0xFA20 | rn_hi_bits) as u16;
3685                let hw2: u16 = (0xF000 | (shift_bits << 8) | r3) as u16;
3686                bytes.extend_from_slice(&hw1.to_le_bytes());
3687                bytes.extend_from_slice(&hw2.to_le_bytes());
3688
3689                // ORR.W shift, shift, R12 (shift = (hi >> (n-32)) | (lo << (64-n)) = new_lo)
3690                let hw1: u16 = (0xEA40 | shift_bits) as u16;
3691                let hw2: u16 = ((shift_bits << 8) | r12) as u16;
3692                bytes.extend_from_slice(&hw1.to_le_bytes());
3693                bytes.extend_from_slice(&hw2.to_le_bytes());
3694
3695                // LSR.W rd_hi, rn_lo, R3 (rd_hi = lo >> (n-32), new_hi high bits)
3696                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3697                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | r3) as u16;
3698                bytes.extend_from_slice(&hw1.to_le_bytes());
3699                bytes.extend_from_slice(&hw2.to_le_bytes());
3700
3701                // ORR.W rd_hi, rd_hi, R4 (rd_hi = (lo >> (n-32)) | (hi << (64-n)) = new_hi)
3702                let hw1: u16 = (0xEA40 | rd_hi_bits) as u16;
3703                let hw2: u16 = ((rd_hi_bits << 8) | r4) as u16;
3704                bytes.extend_from_slice(&hw1.to_le_bytes());
3705                bytes.extend_from_slice(&hw2.to_le_bytes());
3706
3707                // MOV rd_lo, shift (rd_lo = new_lo)
3708                let d_bit = (rd_lo_bits >> 3) & 1;
3709                let mov_instr: u16 =
3710                    (0x4600 | (d_bit << 7) | (shift_bits << 3) | (rd_lo_bits & 0x7)) as u16;
3711                bytes.extend_from_slice(&mov_instr.to_le_bytes());
3712
3713                // POP {R4}
3714                bytes.extend_from_slice(&0xBC10u16.to_le_bytes());
3715
3716                Ok(bytes) // Total: 74 bytes
3717            }
3718
3719            // I64Clz: Count leading zeros in 64-bit value
3720            // If hi != 0: result = CLZ(hi)
3721            // If hi == 0: result = 32 + CLZ(lo)
3722            //
3723            // Layout (using CMP+BNE approach for consistency):
3724            // 0: CMP.W rnhi, #0 (4 bytes)
3725            // 4: BEQ .hi_zero (2 bytes) - branch forward to offset 14
3726            // 6: CLZ.W rd, rnhi (4 bytes)
3727            // 10: B .done (2 bytes) - branch forward to offset 22
3728            // 12: NOP (2 bytes) - padding for alignment
3729            // 14: .hi_zero: CLZ.W rd, rnlo (4 bytes)
3730            // 18: ADD.W rd, rd, #32 (4 bytes)
3731            // 22: .done
3732            ArmOp::I64Clz { rd, rnlo, rnhi } => {
3733                let rd_bits = reg_to_bits(rd);
3734                let rn_lo_bits = reg_to_bits(rnlo);
3735                let rn_hi_bits = reg_to_bits(rnhi);
3736                let mut bytes = Vec::new();
3737
3738                // CMP.W rnhi, #0 (4 bytes at offset 0)
3739                let hw1: u16 = (0xF1B0 | rn_hi_bits) as u16;
3740                let hw2: u16 = 0x0F00;
3741                bytes.extend_from_slice(&hw1.to_le_bytes());
3742                bytes.extend_from_slice(&hw2.to_le_bytes());
3743
3744                // BEQ .hi_zero (2 bytes at offset 4)
3745                // PC = 4 + 4 = 8, target = 14, offset = 6, imm8 = 3
3746                let beq: u16 = 0xD003;
3747                bytes.extend_from_slice(&beq.to_le_bytes());
3748
3749                // CLZ.W rd, rnhi (4 bytes at offset 6)
3750                // CLZ T1: hw1 = 0xFAB<Rm>, hw2 = 0xF<Rd>8<Rm>
3751                let hw1: u16 = (0xFAB0 | rn_hi_bits) as u16;
3752                let hw2: u16 = (0xF080 | (rd_bits << 8) | rn_hi_bits) as u16;
3753                bytes.extend_from_slice(&hw1.to_le_bytes());
3754                bytes.extend_from_slice(&hw2.to_le_bytes());
3755
3756                // B .done (2 bytes at offset 10)
3757                // PC = 10 + 4 = 14, target = 22, offset = 8, imm11 = 4
3758                let b_done: u16 = 0xE004;
3759                bytes.extend_from_slice(&b_done.to_le_bytes());
3760
3761                // NOP (2 bytes at offset 12) - padding
3762                bytes.extend_from_slice(&0xBF00u16.to_le_bytes());
3763
3764                // .hi_zero: (offset 14)
3765                // CLZ.W rd, rnlo (4 bytes)
3766                // CLZ T1: hw1 = 0xFAB<Rm>, hw2 = 0xF<Rd>8<Rm>
3767                let hw1: u16 = (0xFAB0 | rn_lo_bits) as u16;
3768                let hw2: u16 = (0xF080 | (rd_bits << 8) | rn_lo_bits) as u16;
3769                bytes.extend_from_slice(&hw1.to_le_bytes());
3770                bytes.extend_from_slice(&hw2.to_le_bytes());
3771
3772                // ADD.W rd, rd, #32 (4 bytes at offset 18)
3773                let hw1: u16 = (0xF100 | rd_bits) as u16;
3774                let hw2: u16 = ((rd_bits << 8) | 0x20) as u16;
3775                bytes.extend_from_slice(&hw1.to_le_bytes());
3776                bytes.extend_from_slice(&hw2.to_le_bytes());
3777
3778                // .done: (offset 22)
3779                // i64.clz returns i64, so clear high word: MOV rnhi, #0 (2 bytes)
3780                // MOVS Rn, #0: 0010 0 Rn 00000000
3781                let mov0: u16 = (0x2000 | (rn_hi_bits << 8)) as u16;
3782                bytes.extend_from_slice(&mov0.to_le_bytes());
3783
3784                Ok(bytes)
3785            }
3786
3787            // I64Ctz: Count trailing zeros in 64-bit value
3788            // If lo != 0: result = CTZ(lo) = CLZ(RBIT(lo))
3789            // If lo == 0: result = 32 + CTZ(hi) = 32 + CLZ(RBIT(hi))
3790            //
3791            // Layout:
3792            // 0: CMP.W rnlo, #0 (4 bytes)
3793            // 4: BEQ .lo_zero (2 bytes) - branch to offset 18
3794            // 6: RBIT.W rd, rnlo (4 bytes)
3795            // 10: CLZ.W rd, rd (4 bytes)
3796            // 14: B .done (2 bytes) - branch to offset 30
3797            // 16: NOP (2 bytes) - padding
3798            // 18: .lo_zero: RBIT.W rd, rnhi (4 bytes)
3799            // 22: CLZ.W rd, rd (4 bytes)
3800            // 26: ADD.W rd, rd, #32 (4 bytes)
3801            // 30: .done
3802            ArmOp::I64Ctz { rd, rnlo, rnhi } => {
3803                let rd_bits = reg_to_bits(rd);
3804                let rn_lo_bits = reg_to_bits(rnlo);
3805                let rn_hi_bits = reg_to_bits(rnhi);
3806                let mut bytes = Vec::new();
3807
3808                // CMP.W rnlo, #0 (4 bytes at offset 0)
3809                let hw1: u16 = (0xF1B0 | rn_lo_bits) as u16;
3810                let hw2: u16 = 0x0F00;
3811                bytes.extend_from_slice(&hw1.to_le_bytes());
3812                bytes.extend_from_slice(&hw2.to_le_bytes());
3813
3814                // BEQ .lo_zero (2 bytes at offset 4)
3815                // PC = 4 + 4 = 8, target = 18, offset = 10, imm8 = 5
3816                let beq: u16 = 0xD005;
3817                bytes.extend_from_slice(&beq.to_le_bytes());
3818
3819                // RBIT.W rd, rnlo (4 bytes at offset 6)
3820                // RBIT T1: hw1 = 0xFA9<Rm>, hw2 = 0xF<Rd>A<Rm>
3821                let hw1: u16 = (0xFA90 | rn_lo_bits) as u16;
3822                let hw2: u16 = (0xF0A0 | (rd_bits << 8) | rn_lo_bits) as u16;
3823                bytes.extend_from_slice(&hw1.to_le_bytes());
3824                bytes.extend_from_slice(&hw2.to_le_bytes());
3825
3826                // CLZ.W rd, rd (4 bytes at offset 10)
3827                // CLZ T1: hw1 = 0xFAB<Rm>, hw2 = 0xF<Rd>8<Rm>
3828                let hw1: u16 = (0xFAB0 | rd_bits) as u16;
3829                let hw2: u16 = (0xF080 | (rd_bits << 8) | rd_bits) as u16;
3830                bytes.extend_from_slice(&hw1.to_le_bytes());
3831                bytes.extend_from_slice(&hw2.to_le_bytes());
3832
3833                // B .done (2 bytes at offset 14)
3834                // PC = 14 + 4 = 18, target = 30, offset = 12, imm11 = 6
3835                let b_done: u16 = 0xE006;
3836                bytes.extend_from_slice(&b_done.to_le_bytes());
3837
3838                // NOP (2 bytes at offset 16) - padding
3839                bytes.extend_from_slice(&0xBF00u16.to_le_bytes());
3840
3841                // .lo_zero: (offset 18)
3842                // RBIT.W rd, rnhi (4 bytes)
3843                // RBIT T1: hw1 = 0xFA9<Rm>, hw2 = 0xF<Rd>A<Rm>
3844                let hw1: u16 = (0xFA90 | rn_hi_bits) as u16;
3845                let hw2: u16 = (0xF0A0 | (rd_bits << 8) | rn_hi_bits) as u16;
3846                bytes.extend_from_slice(&hw1.to_le_bytes());
3847                bytes.extend_from_slice(&hw2.to_le_bytes());
3848
3849                // CLZ.W rd, rd (4 bytes at offset 22)
3850                // CLZ T1: hw1 = 0xFAB<Rm>, hw2 = 0xF<Rd>8<Rm>
3851                let hw1: u16 = (0xFAB0 | rd_bits) as u16;
3852                let hw2: u16 = (0xF080 | (rd_bits << 8) | rd_bits) as u16;
3853                bytes.extend_from_slice(&hw1.to_le_bytes());
3854                bytes.extend_from_slice(&hw2.to_le_bytes());
3855
3856                // ADD.W rd, rd, #32 (4 bytes at offset 26)
3857                let hw1: u16 = (0xF100 | rd_bits) as u16;
3858                let hw2: u16 = ((rd_bits << 8) | 0x20) as u16;
3859                bytes.extend_from_slice(&hw1.to_le_bytes());
3860                bytes.extend_from_slice(&hw2.to_le_bytes());
3861
3862                // .done: (offset 30)
3863                // i64.ctz returns i64, so clear high word: MOV rnhi, #0 (2 bytes)
3864                let mov0: u16 = (0x2000 | (rn_hi_bits << 8)) as u16;
3865                bytes.extend_from_slice(&mov0.to_le_bytes());
3866
3867                Ok(bytes)
3868            }
3869
3870            // I64Popcnt: Population count of 64-bit value
3871            // result = POPCNT(lo) + POPCNT(hi)
3872            // Using SIMD-style parallel bit counting algorithm
3873            ArmOp::I64Popcnt { rd, rnlo, rnhi } => {
3874                let rd_bits = reg_to_bits(rd);
3875                let rn_lo_bits = reg_to_bits(rnlo);
3876                let rn_hi_bits = reg_to_bits(rnhi);
3877                let r12: u32 = 12; // IP scratch
3878                let r3: u32 = 3; // Scratch for hi popcnt result
3879                let mut bytes = Vec::new();
3880
3881                // PUSH {R3, R4, R5} - save scratch registers
3882                bytes.extend_from_slice(&0xB438u16.to_le_bytes());
3883
3884                // Strategy: compute popcnt(lo) -> R4, popcnt(hi) -> R5, add them -> rd
3885                // Using lookup table approach for each byte would be too large
3886                // Using shift-and-add approach instead
3887
3888                // For simplicity and correctness, use the efficient parallel algorithm
3889                // but implement it as a series of inline operations
3890
3891                // MOV R4, rnlo
3892                let d_bit: u32 = 0; // R4 < 8, so high bit is 0
3893                let mov: u16 = (0x4600 | (d_bit << 7) | (rn_lo_bits << 3) | (4 & 0x7)) as u16;
3894                bytes.extend_from_slice(&mov.to_le_bytes());
3895
3896                // MOV R5, rnhi
3897                let d_bit: u32 = 0; // R5 < 8, so high bit is 0
3898                let mov: u16 = (0x4600 | (d_bit << 7) | (rn_hi_bits << 3) | (5 & 0x7)) as u16;
3899                bytes.extend_from_slice(&mov.to_le_bytes());
3900
3901                // --- POPCNT for R4 (lo word) ---
3902                // Step 1: x = x - ((x >> 1) & 0x55555555)
3903                // LSR.W R12, R4, #1
3904                let hw1: u16 = 0xEA4F;
3905                let hw2: u16 = ((r12 << 8) | 0x50 | 4) as u16;
3906                bytes.extend_from_slice(&hw1.to_le_bytes());
3907                bytes.extend_from_slice(&hw2.to_le_bytes());
3908
3909                // Load 0x55555555 into R3 using MOVW/MOVT
3910                // MOVW R3, #0x5555
3911                bytes.extend_from_slice(&0xF245u16.to_le_bytes());
3912                bytes.extend_from_slice(&0x5355u16.to_le_bytes());
3913                // MOVT R3, #0x5555
3914                bytes.extend_from_slice(&0xF2C5u16.to_le_bytes());
3915                bytes.extend_from_slice(&0x5355u16.to_le_bytes());
3916
3917                // AND.W R12, R12, R3
3918                let hw1: u16 = (0xEA00 | r12) as u16;
3919                let hw2: u16 = ((r12 << 8) | r3) as u16;
3920                bytes.extend_from_slice(&hw1.to_le_bytes());
3921                bytes.extend_from_slice(&hw2.to_le_bytes());
3922
3923                // SUB.W R4, R4, R12
3924                let hw1: u16 = (0xEBA0 | 4) as u16;
3925                let hw2: u16 = ((4 << 8) | r12) as u16;
3926                bytes.extend_from_slice(&hw1.to_le_bytes());
3927                bytes.extend_from_slice(&hw2.to_le_bytes());
3928
3929                // Step 2: x = (x & 0x33333333) + ((x >> 2) & 0x33333333)
3930                // Load 0x33333333 into R3
3931                // MOVW R3, #0x3333
3932                bytes.extend_from_slice(&0xF243u16.to_le_bytes());
3933                bytes.extend_from_slice(&0x3333u16.to_le_bytes());
3934                // MOVT R3, #0x3333
3935                bytes.extend_from_slice(&0xF2C3u16.to_le_bytes());
3936                bytes.extend_from_slice(&0x3333u16.to_le_bytes());
3937
3938                // AND.W R12, R4, R3
3939                let hw1: u16 = (0xEA00 | 4) as u16;
3940                let hw2: u16 = ((r12 << 8) | r3) as u16;
3941                bytes.extend_from_slice(&hw1.to_le_bytes());
3942                bytes.extend_from_slice(&hw2.to_le_bytes());
3943
3944                // LSR.W R4, R4, #2
3945                let hw1: u16 = 0xEA4F;
3946                let hw2: u16 = ((4 << 8) | 0x90 | 4) as u16;
3947                bytes.extend_from_slice(&hw1.to_le_bytes());
3948                bytes.extend_from_slice(&hw2.to_le_bytes());
3949
3950                // AND.W R4, R4, R3
3951                let hw1: u16 = (0xEA00 | 4) as u16;
3952                let hw2: u16 = ((4 << 8) | r3) as u16;
3953                bytes.extend_from_slice(&hw1.to_le_bytes());
3954                bytes.extend_from_slice(&hw2.to_le_bytes());
3955
3956                // ADD.W R4, R4, R12
3957                let hw1: u16 = (0xEB00 | 4) as u16;
3958                let hw2: u16 = ((4 << 8) | r12) as u16;
3959                bytes.extend_from_slice(&hw1.to_le_bytes());
3960                bytes.extend_from_slice(&hw2.to_le_bytes());
3961
3962                // Step 3: x = (x + (x >> 4)) & 0x0F0F0F0F
3963                // LSR.W R12, R4, #4
3964                // hw2 = (imm3 << 12) | (Rd << 8) | (imm2 << 6) | (type << 4) | Rm
3965                // imm5=4=00100 → imm3=1, imm2=0, type=01(LSR)
3966                let hw1: u16 = 0xEA4F;
3967                let hw2: u16 = (0x1000 | (r12 << 8) | 0x10 | 4) as u16;
3968                bytes.extend_from_slice(&hw1.to_le_bytes());
3969                bytes.extend_from_slice(&hw2.to_le_bytes());
3970
3971                // ADD.W R4, R4, R12
3972                let hw1: u16 = (0xEB00 | 4) as u16;
3973                let hw2: u16 = ((4 << 8) | r12) as u16;
3974                bytes.extend_from_slice(&hw1.to_le_bytes());
3975                bytes.extend_from_slice(&hw2.to_le_bytes());
3976
3977                // Load 0x0F0F0F0F into R3
3978                // MOVW R3, #0x0F0F (imm4=0, i=1, imm3=7, imm8=0x0F)
3979                // hw1 = 11110 1 10 0100 0000 = 0xF640
3980                // hw2 = 0 111 0011 00001111 = 0x730F
3981                bytes.extend_from_slice(&0xF640u16.to_le_bytes());
3982                bytes.extend_from_slice(&0x730Fu16.to_le_bytes());
3983                // MOVT R3, #0x0F0F
3984                bytes.extend_from_slice(&0xF6C0u16.to_le_bytes());
3985                bytes.extend_from_slice(&0x730Fu16.to_le_bytes());
3986
3987                // AND.W R4, R4, R3
3988                let hw1: u16 = (0xEA00 | 4) as u16;
3989                let hw2: u16 = ((4 << 8) | r3) as u16;
3990                bytes.extend_from_slice(&hw1.to_le_bytes());
3991                bytes.extend_from_slice(&hw2.to_le_bytes());
3992
3993                // Step 4: x = x * 0x01010101 >> 24
3994                // Load 0x01010101 into R3
3995                // MOVW R3, #0x0101
3996                bytes.extend_from_slice(&0xF240u16.to_le_bytes());
3997                bytes.extend_from_slice(&0x1301u16.to_le_bytes());
3998                // MOVT R3, #0x0101
3999                bytes.extend_from_slice(&0xF2C0u16.to_le_bytes());
4000                bytes.extend_from_slice(&0x1301u16.to_le_bytes());
4001
4002                // MUL R4, R4, R3
4003                // MUL T2: hw1 = 0xFB00|Rn, hw2 = 0xF000|(Rd<<8)|Rm
4004                let hw1: u16 = (0xFB00 | 4) as u16;
4005                let hw2: u16 = (0xF000 | (4 << 8) | r3) as u16;
4006                bytes.extend_from_slice(&hw1.to_le_bytes());
4007                bytes.extend_from_slice(&hw2.to_le_bytes());
4008
4009                // LSR.W R4, R4, #24
4010                // imm5=24=11000 → imm3=6, imm2=0, type=01(LSR)
4011                let hw1: u16 = 0xEA4F;
4012                let hw2: u16 = (0x6000 | (4 << 8) | 0x10 | 4) as u16;
4013                bytes.extend_from_slice(&hw1.to_le_bytes());
4014                bytes.extend_from_slice(&hw2.to_le_bytes());
4015
4016                // --- POPCNT for R5 (hi word) - same algorithm ---
4017                // Step 1
4018                let hw1: u16 = 0xEA4F;
4019                let hw2: u16 = ((r12 << 8) | 0x50 | 5) as u16;
4020                bytes.extend_from_slice(&hw1.to_le_bytes());
4021                bytes.extend_from_slice(&hw2.to_le_bytes());
4022
4023                // Load 0x55555555 into R3
4024                bytes.extend_from_slice(&0xF245u16.to_le_bytes());
4025                bytes.extend_from_slice(&0x5355u16.to_le_bytes());
4026                bytes.extend_from_slice(&0xF2C5u16.to_le_bytes());
4027                bytes.extend_from_slice(&0x5355u16.to_le_bytes());
4028
4029                let hw1: u16 = (0xEA00 | r12) as u16;
4030                let hw2: u16 = ((r12 << 8) | r3) as u16;
4031                bytes.extend_from_slice(&hw1.to_le_bytes());
4032                bytes.extend_from_slice(&hw2.to_le_bytes());
4033
4034                let hw1: u16 = (0xEBA0 | 5) as u16;
4035                let hw2: u16 = ((5 << 8) | r12) as u16;
4036                bytes.extend_from_slice(&hw1.to_le_bytes());
4037                bytes.extend_from_slice(&hw2.to_le_bytes());
4038
4039                // Step 2
4040                bytes.extend_from_slice(&0xF243u16.to_le_bytes());
4041                bytes.extend_from_slice(&0x3333u16.to_le_bytes());
4042                bytes.extend_from_slice(&0xF2C3u16.to_le_bytes());
4043                bytes.extend_from_slice(&0x3333u16.to_le_bytes());
4044
4045                let hw1: u16 = (0xEA00 | 5) as u16;
4046                let hw2: u16 = ((r12 << 8) | r3) as u16;
4047                bytes.extend_from_slice(&hw1.to_le_bytes());
4048                bytes.extend_from_slice(&hw2.to_le_bytes());
4049
4050                let hw1: u16 = 0xEA4F;
4051                let hw2: u16 = ((5 << 8) | 0x90 | 5) as u16;
4052                bytes.extend_from_slice(&hw1.to_le_bytes());
4053                bytes.extend_from_slice(&hw2.to_le_bytes());
4054
4055                let hw1: u16 = (0xEA00 | 5) as u16;
4056                let hw2: u16 = ((5 << 8) | r3) as u16;
4057                bytes.extend_from_slice(&hw1.to_le_bytes());
4058                bytes.extend_from_slice(&hw2.to_le_bytes());
4059
4060                let hw1: u16 = (0xEB00 | 5) as u16;
4061                let hw2: u16 = ((5 << 8) | r12) as u16;
4062                bytes.extend_from_slice(&hw1.to_le_bytes());
4063                bytes.extend_from_slice(&hw2.to_le_bytes());
4064
4065                // Step 3: LSR.W R12, R5, #4
4066                // imm5=4=00100 → imm3=1, imm2=0, type=01(LSR)
4067                let hw1: u16 = 0xEA4F;
4068                let hw2: u16 = (0x1000 | (r12 << 8) | 0x10 | 5) as u16;
4069                bytes.extend_from_slice(&hw1.to_le_bytes());
4070                bytes.extend_from_slice(&hw2.to_le_bytes());
4071
4072                let hw1: u16 = (0xEB00 | 5) as u16;
4073                let hw2: u16 = ((5 << 8) | r12) as u16;
4074                bytes.extend_from_slice(&hw1.to_le_bytes());
4075                bytes.extend_from_slice(&hw2.to_le_bytes());
4076
4077                // Load 0x0F0F0F0F into R3 (for hi-word)
4078                bytes.extend_from_slice(&0xF640u16.to_le_bytes());
4079                bytes.extend_from_slice(&0x730Fu16.to_le_bytes());
4080                bytes.extend_from_slice(&0xF6C0u16.to_le_bytes());
4081                bytes.extend_from_slice(&0x730Fu16.to_le_bytes());
4082
4083                let hw1: u16 = (0xEA00 | 5) as u16;
4084                let hw2: u16 = ((5 << 8) | r3) as u16;
4085                bytes.extend_from_slice(&hw1.to_le_bytes());
4086                bytes.extend_from_slice(&hw2.to_le_bytes());
4087
4088                // Step 4
4089                bytes.extend_from_slice(&0xF240u16.to_le_bytes());
4090                bytes.extend_from_slice(&0x1301u16.to_le_bytes());
4091                bytes.extend_from_slice(&0xF2C0u16.to_le_bytes());
4092                bytes.extend_from_slice(&0x1301u16.to_le_bytes());
4093
4094                // MUL R5, R5, R3
4095                // MUL T2: hw1 = 0xFB00|Rn, hw2 = 0xF000|(Rd<<8)|Rm
4096                let hw1: u16 = (0xFB00 | 5) as u16;
4097                let hw2: u16 = (0xF000 | (5 << 8) | r3) as u16;
4098                bytes.extend_from_slice(&hw1.to_le_bytes());
4099                bytes.extend_from_slice(&hw2.to_le_bytes());
4100
4101                // LSR.W R5, R5, #24
4102                // imm5=24=11000 → imm3=6, imm2=0, type=01(LSR)
4103                let hw1: u16 = 0xEA4F;
4104                let hw2: u16 = (0x6000 | (5 << 8) | 0x10 | 5) as u16;
4105                bytes.extend_from_slice(&hw1.to_le_bytes());
4106                bytes.extend_from_slice(&hw2.to_le_bytes());
4107
4108                // ADD rd, R4, R5 (combine lo and hi counts)
4109                // ADDS Rd, Rn, Rm (T1): 0001 100 Rm Rn Rd = 0x1800 | (Rm<<6) | (Rn<<3) | Rd
4110                let rd_bits_u16 = rd_bits as u16;
4111                let instr: u16 = 0x1800 | (5 << 6) | (4 << 3) | rd_bits_u16;
4112                bytes.extend_from_slice(&instr.to_le_bytes());
4113
4114                // POP {R3, R4, R5}
4115                bytes.extend_from_slice(&0xBC38u16.to_le_bytes());
4116
4117                // i64.popcnt returns i64, so clear high word: MOV rnhi, #0 (2 bytes)
4118                let mov0: u16 = (0x2000 | (rn_hi_bits << 8)) as u16;
4119                bytes.extend_from_slice(&mov0.to_le_bytes());
4120
4121                Ok(bytes)
4122            }
4123
4124            // I64Extend8S: Sign-extend low 8 bits to 64 bits
4125            // Result: rdlo = sign_extend_8(rnlo), rdhi = rdlo >> 31
4126            ArmOp::I64Extend8S { rdlo, rdhi, rnlo } => {
4127                let rdlo_bits = reg_to_bits(rdlo);
4128                let rdhi_bits = reg_to_bits(rdhi);
4129                let rnlo_bits = reg_to_bits(rnlo);
4130                let mut bytes = Vec::new();
4131
4132                // SXTB.W rdlo, rnlo (sign-extend byte to 32-bit)
4133                // SXTB T2: hw1 = 0xFA4F, hw2 = 0xF0<Rd><Rm>
4134                let hw1: u16 = 0xFA4F_u16;
4135                let hw2: u16 = (0xF080 | (rdlo_bits << 8) | rnlo_bits) as u16;
4136                bytes.extend_from_slice(&hw1.to_le_bytes());
4137                bytes.extend_from_slice(&hw2.to_le_bytes());
4138
4139                // ASR.W rdhi, rdlo, #31 (sign-extend to high word)
4140                // ASR (immediate): hw1 = 0xEA4F, hw2 = imm3:Rd:imm2:type:Rm
4141                // For imm5=31: imm3=111, imm2=11, type=10 (ASR)
4142                // hw2 = (7 << 12) | (rdhi << 8) | (3 << 6) | (2 << 4) | rdlo
4143                let hw1: u16 = 0xEA4F;
4144                let hw2: u16 = (0x70E0 | (rdhi_bits << 8) | rdlo_bits) as u16;
4145                bytes.extend_from_slice(&hw1.to_le_bytes());
4146                bytes.extend_from_slice(&hw2.to_le_bytes());
4147
4148                Ok(bytes)
4149            }
4150
4151            // I64Extend16S: Sign-extend low 16 bits to 64 bits
4152            // Result: rdlo = sign_extend_16(rnlo), rdhi = rdlo >> 31
4153            ArmOp::I64Extend16S { rdlo, rdhi, rnlo } => {
4154                let rdlo_bits = reg_to_bits(rdlo);
4155                let rdhi_bits = reg_to_bits(rdhi);
4156                let rnlo_bits = reg_to_bits(rnlo);
4157                let mut bytes = Vec::new();
4158
4159                // SXTH.W rdlo, rnlo (sign-extend halfword to 32-bit)
4160                // SXTH T2: hw1 = 0xFA0F, hw2 = 0xF0<Rd><Rm>
4161                let hw1: u16 = 0xFA0F_u16;
4162                let hw2: u16 = (0xF080 | (rdlo_bits << 8) | rnlo_bits) as u16;
4163                bytes.extend_from_slice(&hw1.to_le_bytes());
4164                bytes.extend_from_slice(&hw2.to_le_bytes());
4165
4166                // ASR.W rdhi, rdlo, #31 (sign-extend to high word)
4167                let hw1: u16 = 0xEA4F;
4168                let hw2: u16 = (0x70E0 | (rdhi_bits << 8) | rdlo_bits) as u16;
4169                bytes.extend_from_slice(&hw1.to_le_bytes());
4170                bytes.extend_from_slice(&hw2.to_le_bytes());
4171
4172                Ok(bytes)
4173            }
4174
4175            // I64Extend32S: Sign-extend low 32 bits to 64 bits
4176            // Result: rdlo = rnlo, rdhi = rnlo >> 31
4177            ArmOp::I64Extend32S { rdlo, rdhi, rnlo } => {
4178                let rdlo_bits = reg_to_bits(rdlo);
4179                let rdhi_bits = reg_to_bits(rdhi);
4180                let rnlo_bits = reg_to_bits(rnlo);
4181                let mut bytes = Vec::new();
4182
4183                // MOV rdlo, rnlo (if different)
4184                if rdlo_bits != rnlo_bits {
4185                    // MOV Rd, Rm (16-bit): 0100 0110 D Rm Rd[2:0]
4186                    let d_bit = ((rdlo_bits >> 3) & 1) as u16;
4187                    let mov: u16 = 0x4600
4188                        | (d_bit << 7)
4189                        | ((rnlo_bits as u16) << 3)
4190                        | ((rdlo_bits & 0x7) as u16);
4191                    bytes.extend_from_slice(&mov.to_le_bytes());
4192                }
4193
4194                // ASR.W rdhi, rnlo, #31 (sign-extend to high word)
4195                let hw1: u16 = 0xEA4F;
4196                let hw2: u16 = (0x70E0 | (rdhi_bits << 8) | rnlo_bits) as u16;
4197                bytes.extend_from_slice(&hw1.to_le_bytes());
4198                bytes.extend_from_slice(&hw2.to_le_bytes());
4199
4200                Ok(bytes)
4201            }
4202
4203            // SelectMove: IT <cond>; MOV{cond} rd, rm
4204            // Conditional move: only execute MOV if condition is true
4205            ArmOp::SelectMove { rd, rm, cond } => {
4206                let rd_bits = reg_to_bits(rd) as u16;
4207                let rm_bits = reg_to_bits(rm) as u16;
4208
4209                // Condition code encoding for IT block
4210                use synth_synthesis::Condition;
4211                let cond_bits: u16 = match cond {
4212                    Condition::EQ => 0x0, // Equal
4213                    Condition::NE => 0x1, // Not equal
4214                    Condition::HS => 0x2, // Higher or same (unsigned >=)
4215                    Condition::LO => 0x3, // Lower (unsigned <)
4216                    Condition::HI => 0x8, // Higher (unsigned >)
4217                    Condition::LS => 0x9, // Lower or same (unsigned <=)
4218                    Condition::GE => 0xA, // Greater or equal (signed)
4219                    Condition::LT => 0xB, // Less than (signed)
4220                    Condition::GT => 0xC, // Greater than (signed)
4221                    Condition::LE => 0xD, // Less or equal (signed)
4222                };
4223
4224                // IT <cond>: single Then block (mask = 0x8 for T only)
4225                // IT instruction: 1011 1111 firstcond mask
4226                let it_instr: u16 = 0xBF00 | (cond_bits << 4) | 0x8;
4227
4228                // MOV Rd, Rm (16-bit): 0100 0110 D Rm Rd[2:0]
4229                // This MOV will only execute if condition is true due to IT block
4230                let d_bit = (rd_bits >> 3) & 1;
4231                let mov_instr: u16 = 0x4600 | (d_bit << 7) | (rm_bits << 3) | (rd_bits & 0x7);
4232
4233                // Emit: IT <cond>, MOV rd, rm
4234                let mut bytes = it_instr.to_le_bytes().to_vec();
4235                bytes.extend_from_slice(&mov_instr.to_le_bytes());
4236                Ok(bytes)
4237            }
4238
4239            // Popcnt: Population count (count set bits)
4240            // ARM Cortex-M has no native POPCNT, so we implement the bit manipulation algorithm:
4241            // x = x - ((x >> 1) & 0x55555555);
4242            // x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
4243            // x = (x + (x >> 4)) & 0x0F0F0F0F;
4244            // x = x + (x >> 8);
4245            // x = x + (x >> 16);
4246            // return x & 0x3F;
4247            //
4248            // Uses rd as working register and R12 as scratch for constants
4249            ArmOp::Popcnt { rd, rm } => {
4250                let mut bytes = Vec::new();
4251
4252                // First, move rm to rd if they're different
4253                if rd != rm {
4254                    let rd_bits = reg_to_bits(rd) as u16;
4255                    let rm_bits = reg_to_bits(rm) as u16;
4256                    // MOV Rd, Rm (16-bit): 0100 0110 D Rm Rd[2:0]
4257                    let d_bit = (rd_bits >> 3) & 1;
4258                    let mov_instr: u16 = 0x4600 | (d_bit << 7) | (rm_bits << 3) | (rd_bits & 0x7);
4259                    bytes.extend_from_slice(&mov_instr.to_le_bytes());
4260                }
4261
4262                // Step 1: x = x - ((x >> 1) & 0x55555555)
4263                // Load 0x55555555 into R12
4264                bytes.extend_from_slice(&self.encode_thumb32_movw_raw(12, 0x5555)?);
4265                bytes.extend_from_slice(&self.encode_thumb32_movt_raw(12, 0x5555)?);
4266
4267                // R12_temp = rd >> 1
4268                // We need a second scratch register. Use R11.
4269                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(11, reg_to_bits(rd), 1)?);
4270
4271                // R11 = R11 & R12 (R11 = (x >> 1) & 0x55555555)
4272                bytes.extend_from_slice(&self.encode_thumb32_and_reg_raw(11, 11, 12)?);
4273
4274                // rd = rd - R11
4275                bytes.extend_from_slice(&self.encode_thumb32_sub_reg_raw(
4276                    reg_to_bits(rd),
4277                    reg_to_bits(rd),
4278                    11,
4279                )?);
4280
4281                // Step 2: x = (x & 0x33333333) + ((x >> 2) & 0x33333333)
4282                // Load 0x33333333 into R12
4283                bytes.extend_from_slice(&self.encode_thumb32_movw_raw(12, 0x3333)?);
4284                bytes.extend_from_slice(&self.encode_thumb32_movt_raw(12, 0x3333)?);
4285
4286                // R11 = rd & R12
4287                bytes.extend_from_slice(&self.encode_thumb32_and_reg_raw(
4288                    11,
4289                    reg_to_bits(rd),
4290                    12,
4291                )?);
4292
4293                // rd = rd >> 2
4294                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(
4295                    reg_to_bits(rd),
4296                    reg_to_bits(rd),
4297                    2,
4298                )?);
4299
4300                // rd = rd & R12
4301                bytes.extend_from_slice(&self.encode_thumb32_and_reg_raw(
4302                    reg_to_bits(rd),
4303                    reg_to_bits(rd),
4304                    12,
4305                )?);
4306
4307                // rd = rd + R11
4308                bytes.extend_from_slice(&self.encode_thumb32_add_reg_raw(
4309                    reg_to_bits(rd),
4310                    reg_to_bits(rd),
4311                    11,
4312                )?);
4313
4314                // Step 3: x = (x + (x >> 4)) & 0x0F0F0F0F
4315                // R11 = rd >> 4
4316                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(11, reg_to_bits(rd), 4)?);
4317
4318                // rd = rd + R11
4319                bytes.extend_from_slice(&self.encode_thumb32_add_reg_raw(
4320                    reg_to_bits(rd),
4321                    reg_to_bits(rd),
4322                    11,
4323                )?);
4324
4325                // Load 0x0F0F0F0F into R12
4326                bytes.extend_from_slice(&self.encode_thumb32_movw_raw(12, 0x0F0F)?);
4327                bytes.extend_from_slice(&self.encode_thumb32_movt_raw(12, 0x0F0F)?);
4328
4329                // rd = rd & R12
4330                bytes.extend_from_slice(&self.encode_thumb32_and_reg_raw(
4331                    reg_to_bits(rd),
4332                    reg_to_bits(rd),
4333                    12,
4334                )?);
4335
4336                // Step 4: x = x + (x >> 8)
4337                // R11 = rd >> 8
4338                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(11, reg_to_bits(rd), 8)?);
4339
4340                // rd = rd + R11
4341                bytes.extend_from_slice(&self.encode_thumb32_add_reg_raw(
4342                    reg_to_bits(rd),
4343                    reg_to_bits(rd),
4344                    11,
4345                )?);
4346
4347                // Step 5: x = x + (x >> 16)
4348                // R11 = rd >> 16
4349                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(11, reg_to_bits(rd), 16)?);
4350
4351                // rd = rd + R11
4352                bytes.extend_from_slice(&self.encode_thumb32_add_reg_raw(
4353                    reg_to_bits(rd),
4354                    reg_to_bits(rd),
4355                    11,
4356                )?);
4357
4358                // Step 6: return x & 0x3F
4359                // AND with 0x3F (small immediate, can use BIC or AND with immediate)
4360                bytes.extend_from_slice(&self.encode_thumb32_and_imm_raw(
4361                    reg_to_bits(rd),
4362                    reg_to_bits(rd),
4363                    0x3F,
4364                )?);
4365
4366                Ok(bytes)
4367            }
4368
4369            // I64DivU: 64-bit unsigned division using binary long division
4370            // Input: R0:R1 = dividend, R2:R3 = divisor
4371            // Output: R0:R1 = quotient
4372            // Uses: R4-R7, R12 as loop counter (avoid R8 for Renode compatibility)
4373            ArmOp::I64DivU {
4374                rdlo: _,
4375                rdhi: _,
4376                rnlo: _,
4377                rnhi: _,
4378                rmlo: _,
4379                rmhi: _,
4380            } => {
4381                let mut bytes = Vec::new();
4382
4383                // PUSH {R4-R7} - save scratch registers (NO LR — this is inline code)
4384                // 16-bit PUSH: 1011 010 M rrrrrrrr where M=0 (no LR), r=R4-R7 = 0xF0
4385                // Encoding: 1011 0100 1111 0000 = 0xB4F0
4386                bytes.extend_from_slice(&0xB4F0u16.to_le_bytes());
4387
4388                // Initialize quotient (R4:R5) = 0
4389                bytes.extend_from_slice(&0x2400u16.to_le_bytes()); // MOV R4, #0
4390                bytes.extend_from_slice(&0x2500u16.to_le_bytes()); // MOV R5, #0
4391
4392                // Initialize remainder (R6:R7) = 0
4393                bytes.extend_from_slice(&0x2600u16.to_le_bytes()); // MOV R6, #0
4394                bytes.extend_from_slice(&0x2700u16.to_le_bytes()); // MOV R7, #0
4395
4396                // Initialize loop counter R12 = 64 (use R12 scratch instead of R8)
4397                // MOV.W R12, #64: F04F 0C40
4398                bytes.extend_from_slice(&0xF04Fu16.to_le_bytes());
4399                bytes.extend_from_slice(&0x0C40u16.to_le_bytes());
4400
4401                // Loop start
4402                let loop_start = bytes.len();
4403
4404                // === Loop body: process one bit ===
4405
4406                // 1. Shift quotient R4:R5 left by 1
4407                // LSLS R5, R5, #1 (16-bit: 0000 0010 1010 1101 = 0x006D -> actually 0x002D for LSL R5,R5,#1)
4408                // LSL Rd, Rm, #imm5: 000 00 imm5 Rm Rd = 000 00 00001 101 101 = 0x006D
4409                bytes.extend_from_slice(&0x006Du16.to_le_bytes()); // LSLS R5, R5, #1
4410                // Get carry from R4 into R5: ORR R5, R5, R4 LSR #31
4411                // Thumb-2 ORR with shifted register: EA45 75D4 = ORR.W R5, R5, R4, LSR #31
4412                // 11101010 010 S Rn | 0 imm3 Rd imm2 type Rm
4413                // type=01 (LSR), imm5=31 (imm3=111, imm2=11)
4414                bytes.extend_from_slice(&0xEA45u16.to_le_bytes());
4415                bytes.extend_from_slice(&0x75D4u16.to_le_bytes()); // ORR.W R5, R5, R4, LSR #31
4416                // LSLS R4, R4, #1: 000 00 00001 100 100 = 0x0064
4417                bytes.extend_from_slice(&0x0064u16.to_le_bytes()); // LSLS R4, R4, #1
4418
4419                // 2. Shift remainder R6:R7 left by 1, OR in MSB of dividend R1
4420                // LSLS R7, R7, #1
4421                bytes.extend_from_slice(&0x007Fu16.to_le_bytes()); // LSLS R7, R7, #1
4422                // ORR.W R7, R7, R6, LSR #31
4423                bytes.extend_from_slice(&0xEA47u16.to_le_bytes());
4424                bytes.extend_from_slice(&0x77D6u16.to_le_bytes());
4425                // LSLS R6, R6, #1
4426                bytes.extend_from_slice(&0x0076u16.to_le_bytes()); // LSLS R6, R6, #1
4427                // ORR.W R6, R6, R1, LSR #31 (bring in MSB of dividend high)
4428                bytes.extend_from_slice(&0xEA46u16.to_le_bytes());
4429                bytes.extend_from_slice(&0x76D1u16.to_le_bytes());
4430
4431                // 3. Shift dividend R0:R1 left by 1
4432                // LSLS R1, R1, #1
4433                bytes.extend_from_slice(&0x0049u16.to_le_bytes()); // LSLS R1, R1, #1
4434                // ORR.W R1, R1, R0, LSR #31
4435                bytes.extend_from_slice(&0xEA41u16.to_le_bytes());
4436                bytes.extend_from_slice(&0x71D0u16.to_le_bytes());
4437                // LSLS R0, R0, #1
4438                bytes.extend_from_slice(&0x0040u16.to_le_bytes()); // LSLS R0, R0, #1
4439
4440                // 4. Compare remainder >= divisor (64-bit unsigned comparison)
4441                // Compare high words first: CMP R7, R3
4442                // CMP Rn, Rm encoding: 0x4280 | (Rm << 3) | Rn
4443                bytes.extend_from_slice(&0x429Fu16.to_le_bytes()); // CMP R7, R3 (16-bit)
4444                // BHI means R7 > R3 (unsigned) - definitely subtract
4445                // BLO means R7 < R3 - definitely don't subtract
4446                // BEQ means need to check low words
4447
4448                // If high > divisor high: branch to subtract (forward +offset)
4449                // BHI.N +6 (skip CMP, skip BLO, do subtract)
4450                // BHI: 1101 1000 offset8 where cond=1000 (HI)
4451                bytes.extend_from_slice(&0xD802u16.to_le_bytes()); // BHI +4 (to subtract block)
4452
4453                // If high < divisor high: branch past subtract
4454                // BLO.N +10 (skip to decrement)
4455                bytes.extend_from_slice(&0xD306u16.to_le_bytes()); // BLO/BCC +12 (past subtract)
4456
4457                // High words equal, compare low: CMP R6, R2
4458                bytes.extend_from_slice(&0x4296u16.to_le_bytes()); // CMP R6, R2 (16-bit)
4459                // BLO/BCC past subtract (skip SUBS+SBC.W+ORR.W = 10 bytes = 4 halfwords from PC+4)
4460                bytes.extend_from_slice(&0xD304u16.to_le_bytes()); // BCC +4 halfwords (past subtract)
4461
4462                // === Subtract block: remainder -= divisor, quotient |= 1 ===
4463                // SUBS R6, R6, R2
4464                bytes.extend_from_slice(&0x1AB6u16.to_le_bytes()); // SUBS R6, R6, R2 (16-bit)
4465                // SBC R7, R7, R3 (with borrow)
4466                // Thumb-2 SBC.W: EB67 0703 = SBC.W R7, R7, R3
4467                bytes.extend_from_slice(&0xEB67u16.to_le_bytes());
4468                bytes.extend_from_slice(&0x0703u16.to_le_bytes());
4469                // ORR R4, R4, #1 (set bit 0 of quotient low)
4470                bytes.extend_from_slice(&0xF044u16.to_le_bytes()); // ORR.W R4, R4, #1
4471                bytes.extend_from_slice(&0x0401u16.to_le_bytes());
4472
4473                // === Decrement counter and loop ===
4474                // SUBS.W R12, R12, #1 (decrement loop counter)
4475                // SUBS.W R12, R12, #1: F1BC 0C01
4476                bytes.extend_from_slice(&0xF1BCu16.to_le_bytes());
4477                bytes.extend_from_slice(&0x0C01u16.to_le_bytes());
4478
4479                // BNE back to loop_start
4480                let branch_offset_bytes = bytes.len() - loop_start + 4; // +4 for pipeline
4481                let offset_halfwords = -((branch_offset_bytes / 2) as i16);
4482                let bne_encoding = 0xD100u16 | ((offset_halfwords as u16) & 0xFF);
4483                bytes.extend_from_slice(&bne_encoding.to_le_bytes());
4484
4485                // === Loop done, move quotient to R0:R1 ===
4486                bytes.extend_from_slice(&0x4620u16.to_le_bytes()); // MOV R0, R4
4487                bytes.extend_from_slice(&0x4629u16.to_le_bytes()); // MOV R1, R5
4488
4489                // POP {R4-R7} - restore scratch registers (NO PC — inline code continues)
4490                // 16-bit POP: 1011 110 P rrrrrrrr where P=0 (no PC), r=R4-R7 = 0xF0
4491                // Encoding: 1011 1100 1111 0000 = 0xBCF0
4492                bytes.extend_from_slice(&0xBCF0u16.to_le_bytes());
4493
4494                Ok(bytes)
4495            }
4496
4497            // I64DivS: 64-bit signed division
4498            // Converts to unsigned, divides, then applies sign
4499            // Input: R0:R1 = dividend (signed), R2:R3 = divisor (signed)
4500            // Output: R0:R1 = quotient (signed)
4501            ArmOp::I64DivS {
4502                rdlo: _,
4503                rdhi: _,
4504                rnlo: _,
4505                rnhi: _,
4506                rmlo: _,
4507                rmhi: _,
4508            } => {
4509                let mut bytes = Vec::new();
4510
4511                // PUSH {R4-R11} - save scratch registers (NO LR — inline code)
4512                bytes.extend_from_slice(&0xE92Du16.to_le_bytes());
4513                bytes.extend_from_slice(&0x0FF0u16.to_le_bytes());
4514
4515                // Save result sign in R9: R9 = R1 XOR R3 (sign bit = MSB)
4516                // EOR.W R9, R1, R3
4517                bytes.extend_from_slice(&0xEA81u16.to_le_bytes());
4518                bytes.extend_from_slice(&0x0903u16.to_le_bytes());
4519
4520                // If dividend negative (R1 MSB set), negate it
4521                // TST R1, R1 (check sign)
4522                bytes.extend_from_slice(&0x4209u16.to_le_bytes()); // TST R1, R1
4523                // BPL skip_neg_dividend (+10 bytes = 5 halfwords)
4524                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4525
4526                // Negate R0:R1 (64-bit): RSBS R0, R0, #0; SBC R1, R1, R1 LSL #1
4527                // Actually: MVN R0, R0; MVN R1, R1; ADDS R0, R0, #1; ADC R1, R1, #0
4528                bytes.extend_from_slice(&0x43C0u16.to_le_bytes()); // MVNS R0, R0
4529                bytes.extend_from_slice(&0x43C9u16.to_le_bytes()); // MVNS R1, R1
4530                bytes.extend_from_slice(&0x1C40u16.to_le_bytes()); // ADDS R0, R0, #1
4531                bytes.extend_from_slice(&0xF141u16.to_le_bytes()); // ADC.W R1, R1, #0
4532                bytes.extend_from_slice(&0x0100u16.to_le_bytes());
4533
4534                // If divisor negative (R3 MSB set), negate it
4535                bytes.extend_from_slice(&0x421Bu16.to_le_bytes()); // TST R3, R3
4536                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4537
4538                // Negate R2:R3
4539                bytes.extend_from_slice(&0x43D2u16.to_le_bytes()); // MVNS R2, R2
4540                bytes.extend_from_slice(&0x43DBu16.to_le_bytes()); // MVNS R3, R3
4541                bytes.extend_from_slice(&0x1C52u16.to_le_bytes()); // ADDS R2, R2, #1
4542                bytes.extend_from_slice(&0xF143u16.to_le_bytes()); // ADC.W R3, R3, #0
4543                bytes.extend_from_slice(&0x0300u16.to_le_bytes());
4544
4545                // === Now do unsigned division (same as I64DivU) ===
4546                // Initialize quotient (R4:R5) = 0
4547                bytes.extend_from_slice(&0x2400u16.to_le_bytes());
4548                bytes.extend_from_slice(&0x2500u16.to_le_bytes());
4549                // Initialize remainder (R6:R7) = 0
4550                bytes.extend_from_slice(&0x2600u16.to_le_bytes());
4551                bytes.extend_from_slice(&0x2700u16.to_le_bytes());
4552                // Initialize loop counter R8 = 64
4553                bytes.extend_from_slice(&0xF04Fu16.to_le_bytes());
4554                bytes.extend_from_slice(&0x0840u16.to_le_bytes());
4555
4556                let loop_start = bytes.len();
4557
4558                // Shift quotient left
4559                bytes.extend_from_slice(&0x006Du16.to_le_bytes()); // LSLS R5, R5, #1
4560                bytes.extend_from_slice(&0xEA45u16.to_le_bytes()); // ORR.W R5, R5, R4, LSR #31
4561                bytes.extend_from_slice(&0x75D4u16.to_le_bytes());
4562                bytes.extend_from_slice(&0x0064u16.to_le_bytes()); // LSLS R4, R4, #1
4563
4564                // Shift remainder left, OR in MSB of dividend
4565                bytes.extend_from_slice(&0x007Fu16.to_le_bytes()); // LSLS R7, R7, #1
4566                bytes.extend_from_slice(&0xEA47u16.to_le_bytes()); // ORR.W R7, R7, R6, LSR #31
4567                bytes.extend_from_slice(&0x77D6u16.to_le_bytes());
4568                bytes.extend_from_slice(&0x0076u16.to_le_bytes()); // LSLS R6, R6, #1
4569                bytes.extend_from_slice(&0xEA46u16.to_le_bytes()); // ORR.W R6, R6, R1, LSR #31
4570                bytes.extend_from_slice(&0x76D1u16.to_le_bytes());
4571
4572                // Shift dividend left
4573                bytes.extend_from_slice(&0x0049u16.to_le_bytes()); // LSLS R1, R1, #1
4574                bytes.extend_from_slice(&0xEA41u16.to_le_bytes()); // ORR.W R1, R1, R0, LSR #31
4575                bytes.extend_from_slice(&0x71D0u16.to_le_bytes());
4576                bytes.extend_from_slice(&0x0040u16.to_le_bytes()); // LSLS R0, R0, #1
4577
4578                // Compare and conditionally subtract
4579                bytes.extend_from_slice(&0x429Fu16.to_le_bytes()); // CMP R7, R3
4580                bytes.extend_from_slice(&0xD802u16.to_le_bytes()); // BHI +4
4581                bytes.extend_from_slice(&0xD306u16.to_le_bytes()); // BCC +12
4582                bytes.extend_from_slice(&0x4296u16.to_le_bytes()); // CMP R6, R2
4583                bytes.extend_from_slice(&0xD304u16.to_le_bytes()); // BCC +4 halfwords
4584
4585                // Subtract and set quotient bit
4586                bytes.extend_from_slice(&0x1AB6u16.to_le_bytes()); // SUBS R6, R6, R2
4587                bytes.extend_from_slice(&0xEB67u16.to_le_bytes()); // SBC.W R7, R7, R3
4588                bytes.extend_from_slice(&0x0703u16.to_le_bytes());
4589                bytes.extend_from_slice(&0xF044u16.to_le_bytes()); // ORR.W R4, R4, #1
4590                bytes.extend_from_slice(&0x0401u16.to_le_bytes());
4591
4592                // Decrement and loop
4593                bytes.extend_from_slice(&0xF1B8u16.to_le_bytes()); // SUB.W R8, R8, #1
4594                bytes.extend_from_slice(&0x0801u16.to_le_bytes());
4595
4596                let branch_offset_bytes = bytes.len() - loop_start + 4;
4597                let offset_halfwords = -((branch_offset_bytes / 2) as i16);
4598                let bne_encoding = 0xD100u16 | ((offset_halfwords as u16) & 0xFF);
4599                bytes.extend_from_slice(&bne_encoding.to_le_bytes());
4600
4601                // Move quotient to R0:R1
4602                bytes.extend_from_slice(&0x4620u16.to_le_bytes()); // MOV R0, R4
4603                bytes.extend_from_slice(&0x4629u16.to_le_bytes()); // MOV R1, R5
4604
4605                // If result should be negative (R9 MSB set), negate R0:R1
4606                bytes.extend_from_slice(&0xF1B9u16.to_le_bytes()); // TST.W R9, R9 (check MSB)
4607                bytes.extend_from_slice(&0x0F00u16.to_le_bytes());
4608                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8 (skip negation)
4609
4610                // Negate result R0:R1
4611                bytes.extend_from_slice(&0x43C0u16.to_le_bytes()); // MVNS R0, R0
4612                bytes.extend_from_slice(&0x43C9u16.to_le_bytes()); // MVNS R1, R1
4613                bytes.extend_from_slice(&0x1C40u16.to_le_bytes()); // ADDS R0, R0, #1
4614                bytes.extend_from_slice(&0xF141u16.to_le_bytes()); // ADC.W R1, R1, #0
4615                bytes.extend_from_slice(&0x0100u16.to_le_bytes());
4616
4617                // POP {R4-R11} - restore scratch registers (NO PC — inline code continues)
4618                bytes.extend_from_slice(&0xE8BDu16.to_le_bytes());
4619                bytes.extend_from_slice(&0x0FF0u16.to_le_bytes());
4620
4621                Ok(bytes)
4622            }
4623
4624            // I64RemU: 64-bit unsigned remainder using binary long division
4625            // Same algorithm as I64DivU but returns remainder instead of quotient
4626            // Input: R0:R1 = dividend, R2:R3 = divisor
4627            // Output: R0:R1 = remainder
4628            ArmOp::I64RemU {
4629                rdlo: _,
4630                rdhi: _,
4631                rnlo: _,
4632                rnhi: _,
4633                rmlo: _,
4634                rmhi: _,
4635            } => {
4636                let mut bytes = Vec::new();
4637
4638                // PUSH {R4-R8} - save scratch registers (NO LR — inline code)
4639                bytes.extend_from_slice(&0xE92Du16.to_le_bytes());
4640                bytes.extend_from_slice(&0x01F0u16.to_le_bytes());
4641
4642                // Initialize quotient (R4:R5) = 0 (computed but not returned)
4643                bytes.extend_from_slice(&0x2400u16.to_le_bytes());
4644                bytes.extend_from_slice(&0x2500u16.to_le_bytes());
4645                // Initialize remainder (R6:R7) = 0
4646                bytes.extend_from_slice(&0x2600u16.to_le_bytes());
4647                bytes.extend_from_slice(&0x2700u16.to_le_bytes());
4648                // Initialize loop counter R8 = 64
4649                bytes.extend_from_slice(&0xF04Fu16.to_le_bytes());
4650                bytes.extend_from_slice(&0x0840u16.to_le_bytes());
4651
4652                let loop_start = bytes.len();
4653
4654                // Shift quotient left (not needed for result, but keeps algorithm same)
4655                bytes.extend_from_slice(&0x006Du16.to_le_bytes()); // LSLS R5, R5, #1
4656                bytes.extend_from_slice(&0xEA45u16.to_le_bytes()); // ORR.W R5, R5, R4, LSR #31
4657                bytes.extend_from_slice(&0x75D4u16.to_le_bytes());
4658                bytes.extend_from_slice(&0x0064u16.to_le_bytes()); // LSLS R4, R4, #1
4659
4660                // Shift remainder left, OR in MSB of dividend
4661                bytes.extend_from_slice(&0x007Fu16.to_le_bytes()); // LSLS R7, R7, #1
4662                bytes.extend_from_slice(&0xEA47u16.to_le_bytes()); // ORR.W R7, R7, R6, LSR #31
4663                bytes.extend_from_slice(&0x77D6u16.to_le_bytes());
4664                bytes.extend_from_slice(&0x0076u16.to_le_bytes()); // LSLS R6, R6, #1
4665                bytes.extend_from_slice(&0xEA46u16.to_le_bytes()); // ORR.W R6, R6, R1, LSR #31
4666                bytes.extend_from_slice(&0x76D1u16.to_le_bytes());
4667
4668                // Shift dividend left
4669                bytes.extend_from_slice(&0x0049u16.to_le_bytes()); // LSLS R1, R1, #1
4670                bytes.extend_from_slice(&0xEA41u16.to_le_bytes()); // ORR.W R1, R1, R0, LSR #31
4671                bytes.extend_from_slice(&0x71D0u16.to_le_bytes());
4672                bytes.extend_from_slice(&0x0040u16.to_le_bytes()); // LSLS R0, R0, #1
4673
4674                // Compare and conditionally subtract
4675                bytes.extend_from_slice(&0x429Fu16.to_le_bytes()); // CMP R7, R3
4676                bytes.extend_from_slice(&0xD802u16.to_le_bytes()); // BHI +4
4677                bytes.extend_from_slice(&0xD306u16.to_le_bytes()); // BCC +12
4678                bytes.extend_from_slice(&0x4296u16.to_le_bytes()); // CMP R6, R2
4679                bytes.extend_from_slice(&0xD304u16.to_le_bytes()); // BCC +4 halfwords
4680
4681                // Subtract and set quotient bit
4682                bytes.extend_from_slice(&0x1AB6u16.to_le_bytes()); // SUBS R6, R6, R2
4683                bytes.extend_from_slice(&0xEB67u16.to_le_bytes()); // SBC.W R7, R7, R3
4684                bytes.extend_from_slice(&0x0703u16.to_le_bytes());
4685                bytes.extend_from_slice(&0xF044u16.to_le_bytes()); // ORR.W R4, R4, #1
4686                bytes.extend_from_slice(&0x0401u16.to_le_bytes());
4687
4688                // Decrement and loop
4689                bytes.extend_from_slice(&0xF1B8u16.to_le_bytes()); // SUB.W R8, R8, #1
4690                bytes.extend_from_slice(&0x0801u16.to_le_bytes());
4691
4692                let branch_offset_bytes = bytes.len() - loop_start + 4;
4693                let offset_halfwords = -((branch_offset_bytes / 2) as i16);
4694                let bne_encoding = 0xD100u16 | ((offset_halfwords as u16) & 0xFF);
4695                bytes.extend_from_slice(&bne_encoding.to_le_bytes());
4696
4697                // Move REMAINDER to R0:R1 (difference from I64DivU)
4698                bytes.extend_from_slice(&0x4630u16.to_le_bytes()); // MOV R0, R6
4699                bytes.extend_from_slice(&0x4639u16.to_le_bytes()); // MOV R1, R7
4700
4701                // POP {R4-R8} - restore scratch registers (NO PC — inline code continues)
4702                bytes.extend_from_slice(&0xE8BDu16.to_le_bytes());
4703                bytes.extend_from_slice(&0x01F0u16.to_le_bytes());
4704
4705                Ok(bytes)
4706            }
4707
4708            // I64RemS: 64-bit signed remainder
4709            // Remainder sign follows dividend sign (not quotient rule)
4710            // Input: R0:R1 = dividend (signed), R2:R3 = divisor (signed)
4711            // Output: R0:R1 = remainder (signed, same sign as dividend)
4712            ArmOp::I64RemS {
4713                rdlo: _,
4714                rdhi: _,
4715                rnlo: _,
4716                rnhi: _,
4717                rmlo: _,
4718                rmhi: _,
4719            } => {
4720                let mut bytes = Vec::new();
4721
4722                // PUSH {R4-R11} - save scratch registers (NO LR — inline code)
4723                bytes.extend_from_slice(&0xE92Du16.to_le_bytes());
4724                bytes.extend_from_slice(&0x0FF0u16.to_le_bytes());
4725
4726                // Save dividend sign in R9 (remainder sign = dividend sign)
4727                // MOV R9, R1 (just need the sign bit)
4728                bytes.extend_from_slice(&0x4689u16.to_le_bytes()); // MOV R9, R1
4729
4730                // If dividend negative (R1 MSB set), negate it
4731                bytes.extend_from_slice(&0x4209u16.to_le_bytes()); // TST R1, R1
4732                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4733
4734                // Negate R0:R1
4735                bytes.extend_from_slice(&0x43C0u16.to_le_bytes()); // MVNS R0, R0
4736                bytes.extend_from_slice(&0x43C9u16.to_le_bytes()); // MVNS R1, R1
4737                bytes.extend_from_slice(&0x1C40u16.to_le_bytes()); // ADDS R0, R0, #1
4738                bytes.extend_from_slice(&0xF141u16.to_le_bytes()); // ADC.W R1, R1, #0
4739                bytes.extend_from_slice(&0x0100u16.to_le_bytes());
4740
4741                // If divisor negative (R3 MSB set), negate it
4742                bytes.extend_from_slice(&0x421Bu16.to_le_bytes()); // TST R3, R3
4743                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4744
4745                // Negate R2:R3
4746                bytes.extend_from_slice(&0x43D2u16.to_le_bytes()); // MVNS R2, R2
4747                bytes.extend_from_slice(&0x43DBu16.to_le_bytes()); // MVNS R3, R3
4748                bytes.extend_from_slice(&0x1C52u16.to_le_bytes()); // ADDS R2, R2, #1
4749                bytes.extend_from_slice(&0xF143u16.to_le_bytes()); // ADC.W R3, R3, #0
4750                bytes.extend_from_slice(&0x0300u16.to_le_bytes());
4751
4752                // === Unsigned division algorithm ===
4753                // Initialize quotient (R4:R5) = 0
4754                bytes.extend_from_slice(&0x2400u16.to_le_bytes());
4755                bytes.extend_from_slice(&0x2500u16.to_le_bytes());
4756                // Initialize remainder (R6:R7) = 0
4757                bytes.extend_from_slice(&0x2600u16.to_le_bytes());
4758                bytes.extend_from_slice(&0x2700u16.to_le_bytes());
4759                // Initialize loop counter R8 = 64
4760                bytes.extend_from_slice(&0xF04Fu16.to_le_bytes());
4761                bytes.extend_from_slice(&0x0840u16.to_le_bytes());
4762
4763                let loop_start = bytes.len();
4764
4765                // Shift quotient left
4766                bytes.extend_from_slice(&0x006Du16.to_le_bytes()); // LSLS R5, R5, #1
4767                bytes.extend_from_slice(&0xEA45u16.to_le_bytes()); // ORR.W R5, R5, R4, LSR #31
4768                bytes.extend_from_slice(&0x75D4u16.to_le_bytes());
4769                bytes.extend_from_slice(&0x0064u16.to_le_bytes()); // LSLS R4, R4, #1
4770
4771                // Shift remainder left, OR in MSB of dividend
4772                bytes.extend_from_slice(&0x007Fu16.to_le_bytes()); // LSLS R7, R7, #1
4773                bytes.extend_from_slice(&0xEA47u16.to_le_bytes()); // ORR.W R7, R7, R6, LSR #31
4774                bytes.extend_from_slice(&0x77D6u16.to_le_bytes());
4775                bytes.extend_from_slice(&0x0076u16.to_le_bytes()); // LSLS R6, R6, #1
4776                bytes.extend_from_slice(&0xEA46u16.to_le_bytes()); // ORR.W R6, R6, R1, LSR #31
4777                bytes.extend_from_slice(&0x76D1u16.to_le_bytes());
4778
4779                // Shift dividend left
4780                bytes.extend_from_slice(&0x0049u16.to_le_bytes()); // LSLS R1, R1, #1
4781                bytes.extend_from_slice(&0xEA41u16.to_le_bytes()); // ORR.W R1, R1, R0, LSR #31
4782                bytes.extend_from_slice(&0x71D0u16.to_le_bytes());
4783                bytes.extend_from_slice(&0x0040u16.to_le_bytes()); // LSLS R0, R0, #1
4784
4785                // Compare and conditionally subtract
4786                bytes.extend_from_slice(&0x429Fu16.to_le_bytes()); // CMP R7, R3
4787                bytes.extend_from_slice(&0xD802u16.to_le_bytes()); // BHI +4
4788                bytes.extend_from_slice(&0xD306u16.to_le_bytes()); // BCC +12
4789                bytes.extend_from_slice(&0x4296u16.to_le_bytes()); // CMP R6, R2
4790                bytes.extend_from_slice(&0xD304u16.to_le_bytes()); // BCC +4 halfwords
4791
4792                // Subtract and set quotient bit
4793                bytes.extend_from_slice(&0x1AB6u16.to_le_bytes()); // SUBS R6, R6, R2
4794                bytes.extend_from_slice(&0xEB67u16.to_le_bytes()); // SBC.W R7, R7, R3
4795                bytes.extend_from_slice(&0x0703u16.to_le_bytes());
4796                bytes.extend_from_slice(&0xF044u16.to_le_bytes()); // ORR.W R4, R4, #1
4797                bytes.extend_from_slice(&0x0401u16.to_le_bytes());
4798
4799                // Decrement and loop
4800                bytes.extend_from_slice(&0xF1B8u16.to_le_bytes()); // SUB.W R8, R8, #1
4801                bytes.extend_from_slice(&0x0801u16.to_le_bytes());
4802
4803                let branch_offset_bytes = bytes.len() - loop_start + 4;
4804                let offset_halfwords = -((branch_offset_bytes / 2) as i16);
4805                let bne_encoding = 0xD100u16 | ((offset_halfwords as u16) & 0xFF);
4806                bytes.extend_from_slice(&bne_encoding.to_le_bytes());
4807
4808                // Move remainder to R0:R1
4809                bytes.extend_from_slice(&0x4630u16.to_le_bytes()); // MOV R0, R6
4810                bytes.extend_from_slice(&0x4639u16.to_le_bytes()); // MOV R1, R7
4811
4812                // If original dividend was negative (R9 MSB set), negate remainder
4813                bytes.extend_from_slice(&0xF1B9u16.to_le_bytes()); // TST.W R9, R9
4814                bytes.extend_from_slice(&0x0F00u16.to_le_bytes());
4815                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4816
4817                // Negate result R0:R1
4818                bytes.extend_from_slice(&0x43C0u16.to_le_bytes()); // MVNS R0, R0
4819                bytes.extend_from_slice(&0x43C9u16.to_le_bytes()); // MVNS R1, R1
4820                bytes.extend_from_slice(&0x1C40u16.to_le_bytes()); // ADDS R0, R0, #1
4821                bytes.extend_from_slice(&0xF141u16.to_le_bytes()); // ADC.W R1, R1, #0
4822                bytes.extend_from_slice(&0x0100u16.to_le_bytes());
4823
4824                // POP {R4-R11} - restore scratch registers (NO PC — inline code continues)
4825                bytes.extend_from_slice(&0xE8BDu16.to_le_bytes());
4826                bytes.extend_from_slice(&0x0FF0u16.to_le_bytes());
4827
4828                Ok(bytes)
4829            }
4830
4831            // === F32 VFP single-precision Thumb-2 encodings ===
4832            // VFP instruction words are identical to ARM32; emit as two LE halfwords.
4833            ArmOp::F32Add { sd, sn, sm } => {
4834                Ok(vfp_to_thumb_bytes(encode_vfp_3reg(0xEE300A00, sd, sn, sm)?))
4835            }
4836            ArmOp::F32Sub { sd, sn, sm } => {
4837                Ok(vfp_to_thumb_bytes(encode_vfp_3reg(0xEE300A40, sd, sn, sm)?))
4838            }
4839            ArmOp::F32Mul { sd, sn, sm } => {
4840                Ok(vfp_to_thumb_bytes(encode_vfp_3reg(0xEE200A00, sd, sn, sm)?))
4841            }
4842            ArmOp::F32Div { sd, sn, sm } => {
4843                Ok(vfp_to_thumb_bytes(encode_vfp_3reg(0xEE800A00, sd, sn, sm)?))
4844            }
4845            ArmOp::F32Abs { sd, sm } => {
4846                Ok(vfp_to_thumb_bytes(encode_vfp_2reg(0xEEB00AC0, sd, sm)?))
4847            }
4848            ArmOp::F32Neg { sd, sm } => {
4849                Ok(vfp_to_thumb_bytes(encode_vfp_2reg(0xEEB10A40, sd, sm)?))
4850            }
4851            ArmOp::F32Sqrt { sd, sm } => {
4852                Ok(vfp_to_thumb_bytes(encode_vfp_2reg(0xEEB10AC0, sd, sm)?))
4853            }
4854
4855            // f32 pseudo-ops — multi-instruction sequences
4856            // FPSCR RMode: 00=nearest, 01=+inf(ceil), 10=-inf(floor), 11=zero(trunc)
4857            ArmOp::F32Ceil { sd, sm } => self.encode_thumb_f32_rounding(sd, sm, 0b01),
4858            ArmOp::F32Floor { sd, sm } => self.encode_thumb_f32_rounding(sd, sm, 0b10),
4859            ArmOp::F32Trunc { sd, sm } => self.encode_thumb_f32_rounding(sd, sm, 0b11),
4860            ArmOp::F32Nearest { sd, sm } => self.encode_thumb_f32_rounding(sd, sm, 0b00),
4861            ArmOp::F32Min { sd, sn, sm } => self.encode_thumb_f32_minmax(sd, sn, sm, true),
4862            ArmOp::F32Max { sd, sn, sm } => self.encode_thumb_f32_minmax(sd, sn, sm, false),
4863            ArmOp::F32Copysign { sd, sn, sm } => self.encode_thumb_f32_copysign(sd, sn, sm),
4864
4865            // f32 comparisons — VCMP + VMRS + MOV #0 + IT + MOV #1
4866            ArmOp::F32Eq { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0x0),
4867            ArmOp::F32Ne { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0x1),
4868            ArmOp::F32Lt { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0x4),
4869            ArmOp::F32Le { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0x9),
4870            ArmOp::F32Gt { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0xC),
4871            ArmOp::F32Ge { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0xA),
4872
4873            ArmOp::F32Const { sd, value } => self.encode_thumb_f32_const(sd, *value),
4874
4875            ArmOp::F32Load { sd, addr } => {
4876                Ok(vfp_to_thumb_bytes(encode_vfp_ldst(0xED900A00, sd, addr)?))
4877            }
4878            ArmOp::F32Store { sd, addr } => {
4879                Ok(vfp_to_thumb_bytes(encode_vfp_ldst(0xED800A00, sd, addr)?))
4880            }
4881
4882            ArmOp::F32ConvertI32S { sd, rm } => self.encode_thumb_f32_convert_i32(sd, rm, true),
4883            ArmOp::F32ConvertI32U { sd, rm } => self.encode_thumb_f32_convert_i32(sd, rm, false),
4884            ArmOp::F32ConvertI64S { .. } | ArmOp::F32ConvertI64U { .. } => {
4885                Err(synth_core::Error::synthesis(
4886                    "F32 i64 conversion not supported (requires register pairs on 32-bit ARM)",
4887                ))
4888            }
4889            ArmOp::F32ReinterpretI32 { sd, rm } => {
4890                Ok(vfp_to_thumb_bytes(encode_vmov_core_sreg(true, sd, rm)?))
4891            }
4892            ArmOp::I32ReinterpretF32 { rd, sm } => {
4893                Ok(vfp_to_thumb_bytes(encode_vmov_core_sreg(false, sm, rd)?))
4894            }
4895            ArmOp::I32TruncF32S { rd, sm } => self.encode_thumb_i32_trunc_f32(rd, sm, true),
4896            ArmOp::I32TruncF32U { rd, sm } => self.encode_thumb_i32_trunc_f32(rd, sm, false),
4897
4898            // === F64 VFP double-precision Thumb-2 encodings ===
4899            // VFP instruction words are identical to ARM32; emit as two LE halfwords.
4900            ArmOp::F64Add { dd, dn, dm } => Ok(vfp_to_thumb_bytes(encode_vfp_3reg_f64(
4901                0xEE300B00, dd, dn, dm,
4902            )?)),
4903            ArmOp::F64Sub { dd, dn, dm } => Ok(vfp_to_thumb_bytes(encode_vfp_3reg_f64(
4904                0xEE300B40, dd, dn, dm,
4905            )?)),
4906            ArmOp::F64Mul { dd, dn, dm } => Ok(vfp_to_thumb_bytes(encode_vfp_3reg_f64(
4907                0xEE200B00, dd, dn, dm,
4908            )?)),
4909            ArmOp::F64Div { dd, dn, dm } => Ok(vfp_to_thumb_bytes(encode_vfp_3reg_f64(
4910                0xEE800B00, dd, dn, dm,
4911            )?)),
4912            ArmOp::F64Abs { dd, dm } => {
4913                Ok(vfp_to_thumb_bytes(encode_vfp_2reg_f64(0xEEB00BC0, dd, dm)?))
4914            }
4915            ArmOp::F64Neg { dd, dm } => {
4916                Ok(vfp_to_thumb_bytes(encode_vfp_2reg_f64(0xEEB10B40, dd, dm)?))
4917            }
4918            ArmOp::F64Sqrt { dd, dm } => {
4919                Ok(vfp_to_thumb_bytes(encode_vfp_2reg_f64(0xEEB10BC0, dd, dm)?))
4920            }
4921
4922            // f64 pseudo-ops
4923            // FPSCR RMode: 00=nearest, 01=+inf(ceil), 10=-inf(floor), 11=zero(trunc)
4924            ArmOp::F64Ceil { dd, dm } => self.encode_thumb_f64_rounding(dd, dm, 0b01),
4925            ArmOp::F64Floor { dd, dm } => self.encode_thumb_f64_rounding(dd, dm, 0b10),
4926            ArmOp::F64Trunc { dd, dm } => self.encode_thumb_f64_rounding(dd, dm, 0b11),
4927            ArmOp::F64Nearest { dd, dm } => self.encode_thumb_f64_rounding(dd, dm, 0b00),
4928            ArmOp::F64Min { dd, dn, dm } => self.encode_thumb_f64_minmax(dd, dn, dm, true),
4929            ArmOp::F64Max { dd, dn, dm } => self.encode_thumb_f64_minmax(dd, dn, dm, false),
4930            ArmOp::F64Copysign { dd, dn, dm } => self.encode_thumb_f64_copysign(dd, dn, dm),
4931
4932            // f64 comparisons
4933            ArmOp::F64Eq { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0x0),
4934            ArmOp::F64Ne { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0x1),
4935            ArmOp::F64Lt { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0x4),
4936            ArmOp::F64Le { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0x9),
4937            ArmOp::F64Gt { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0xC),
4938            ArmOp::F64Ge { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0xA),
4939
4940            ArmOp::F64Const { dd, value } => self.encode_thumb_f64_const(dd, *value),
4941
4942            ArmOp::F64Load { dd, addr } => Ok(vfp_to_thumb_bytes(encode_vfp_ldst_f64(
4943                0xED900B00, dd, addr,
4944            )?)),
4945            ArmOp::F64Store { dd, addr } => Ok(vfp_to_thumb_bytes(encode_vfp_ldst_f64(
4946                0xED800B00, dd, addr,
4947            )?)),
4948
4949            ArmOp::F64ConvertI32S { dd, rm } => self.encode_thumb_f64_convert_i32(dd, rm, true),
4950            ArmOp::F64ConvertI32U { dd, rm } => self.encode_thumb_f64_convert_i32(dd, rm, false),
4951            ArmOp::F64ConvertI64S { .. } | ArmOp::F64ConvertI64U { .. } => {
4952                Err(synth_core::Error::synthesis(
4953                    "F64 i64 conversion not supported (requires register pairs on 32-bit ARM)",
4954                ))
4955            }
4956            ArmOp::F64PromoteF32 { dd, sm } => self.encode_thumb_f64_promote_f32(dd, sm),
4957            ArmOp::F64ReinterpretI64 { dd, rmlo, rmhi } => Ok(vfp_to_thumb_bytes(
4958                encode_vmov_core_dreg(true, dd, rmlo, rmhi)?,
4959            )),
4960            ArmOp::I64ReinterpretF64 { rdlo, rdhi, dm } => Ok(vfp_to_thumb_bytes(
4961                encode_vmov_core_dreg(false, dm, rdlo, rdhi)?,
4962            )),
4963            ArmOp::I64TruncF64S { .. } | ArmOp::I64TruncF64U { .. } => {
4964                Err(synth_core::Error::synthesis(
4965                    "i64 truncation from F64 not supported (requires i64 register pairs on 32-bit ARM)",
4966                ))
4967            }
4968            ArmOp::I32TruncF64S { rd, dm } => self.encode_thumb_i32_trunc_f64(rd, dm, true),
4969            ArmOp::I32TruncF64U { rd, dm } => self.encode_thumb_i32_trunc_f64(rd, dm, false),
4970
4971            // ===== i64 operations: encode as multi-instruction Thumb-2 sequences =====
4972
4973            // I64Add: ADDS rdlo, rnlo, rmlo; ADC.W rdhi, rnhi, rmhi
4974            ArmOp::I64Add {
4975                rdlo,
4976                rdhi,
4977                rnlo,
4978                rnhi,
4979                rmlo,
4980                rmhi,
4981            } => {
4982                let mut bytes = Vec::new();
4983                // ADDS rdlo, rnlo, rmlo (16-bit)
4984                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Adds {
4985                    rd: *rdlo,
4986                    rn: *rnlo,
4987                    op2: Operand2::Reg(*rmlo),
4988                })?);
4989                // ADC.W rdhi, rnhi, rmhi (32-bit)
4990                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Adc {
4991                    rd: *rdhi,
4992                    rn: *rnhi,
4993                    op2: Operand2::Reg(*rmhi),
4994                })?);
4995                Ok(bytes)
4996            }
4997
4998            // I64Sub: SUBS rdlo, rnlo, rmlo; SBC.W rdhi, rnhi, rmhi
4999            ArmOp::I64Sub {
5000                rdlo,
5001                rdhi,
5002                rnlo,
5003                rnhi,
5004                rmlo,
5005                rmhi,
5006            } => {
5007                let mut bytes = Vec::new();
5008                // SUBS rdlo, rnlo, rmlo (16-bit)
5009                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Subs {
5010                    rd: *rdlo,
5011                    rn: *rnlo,
5012                    op2: Operand2::Reg(*rmlo),
5013                })?);
5014                // SBC.W rdhi, rnhi, rmhi (32-bit)
5015                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Sbc {
5016                    rd: *rdhi,
5017                    rn: *rnhi,
5018                    op2: Operand2::Reg(*rmhi),
5019                })?);
5020                Ok(bytes)
5021            }
5022
5023            // I64And: AND rdlo, rnlo, rmlo; AND rdhi, rnhi, rmhi
5024            ArmOp::I64And {
5025                rdlo,
5026                rdhi,
5027                rnlo,
5028                rnhi,
5029                rmlo,
5030                rmhi,
5031            } => {
5032                let mut bytes = Vec::new();
5033                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::And {
5034                    rd: *rdlo,
5035                    rn: *rnlo,
5036                    op2: Operand2::Reg(*rmlo),
5037                })?);
5038                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::And {
5039                    rd: *rdhi,
5040                    rn: *rnhi,
5041                    op2: Operand2::Reg(*rmhi),
5042                })?);
5043                Ok(bytes)
5044            }
5045
5046            // I64Or: ORR rdlo, rnlo, rmlo; ORR rdhi, rnhi, rmhi
5047            ArmOp::I64Or {
5048                rdlo,
5049                rdhi,
5050                rnlo,
5051                rnhi,
5052                rmlo,
5053                rmhi,
5054            } => {
5055                let mut bytes = Vec::new();
5056                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Orr {
5057                    rd: *rdlo,
5058                    rn: *rnlo,
5059                    op2: Operand2::Reg(*rmlo),
5060                })?);
5061                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Orr {
5062                    rd: *rdhi,
5063                    rn: *rnhi,
5064                    op2: Operand2::Reg(*rmhi),
5065                })?);
5066                Ok(bytes)
5067            }
5068
5069            // I64Xor: EOR rdlo, rnlo, rmlo; EOR rdhi, rnhi, rmhi
5070            ArmOp::I64Xor {
5071                rdlo,
5072                rdhi,
5073                rnlo,
5074                rnhi,
5075                rmlo,
5076                rmhi,
5077            } => {
5078                let mut bytes = Vec::new();
5079                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Eor {
5080                    rd: *rdlo,
5081                    rn: *rnlo,
5082                    op2: Operand2::Reg(*rmlo),
5083                })?);
5084                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Eor {
5085                    rd: *rdhi,
5086                    rn: *rnhi,
5087                    op2: Operand2::Reg(*rmhi),
5088                })?);
5089                Ok(bytes)
5090            }
5091
5092            // I64Eqz: ORR scratch, lo, hi; ITE EQ; MOV rd, #1; MOV rd, #0
5093            ArmOp::I64Eqz { rd, rnlo, rnhi } => self.encode_thumb(&ArmOp::I64SetCondZ {
5094                rd: *rd,
5095                rn_lo: *rnlo,
5096                rn_hi: *rnhi,
5097            }),
5098
5099            // I64 comparisons: delegate to I64SetCond
5100            ArmOp::I64Eq {
5101                rd,
5102                rnlo,
5103                rnhi,
5104                rmlo,
5105                rmhi,
5106            } => self.encode_thumb(&ArmOp::I64SetCond {
5107                rd: *rd,
5108                rn_lo: *rnlo,
5109                rn_hi: *rnhi,
5110                rm_lo: *rmlo,
5111                rm_hi: *rmhi,
5112                cond: synth_synthesis::Condition::EQ,
5113            }),
5114
5115            ArmOp::I64Ne {
5116                rd,
5117                rnlo,
5118                rnhi,
5119                rmlo,
5120                rmhi,
5121            } => self.encode_thumb(&ArmOp::I64SetCond {
5122                rd: *rd,
5123                rn_lo: *rnlo,
5124                rn_hi: *rnhi,
5125                rm_lo: *rmlo,
5126                rm_hi: *rmhi,
5127                cond: synth_synthesis::Condition::NE,
5128            }),
5129
5130            ArmOp::I64LtS {
5131                rd,
5132                rnlo,
5133                rnhi,
5134                rmlo,
5135                rmhi,
5136            } => self.encode_thumb(&ArmOp::I64SetCond {
5137                rd: *rd,
5138                rn_lo: *rnlo,
5139                rn_hi: *rnhi,
5140                rm_lo: *rmlo,
5141                rm_hi: *rmhi,
5142                cond: synth_synthesis::Condition::LT,
5143            }),
5144
5145            ArmOp::I64LtU {
5146                rd,
5147                rnlo,
5148                rnhi,
5149                rmlo,
5150                rmhi,
5151            } => self.encode_thumb(&ArmOp::I64SetCond {
5152                rd: *rd,
5153                rn_lo: *rnlo,
5154                rn_hi: *rnhi,
5155                rm_lo: *rmlo,
5156                rm_hi: *rmhi,
5157                cond: synth_synthesis::Condition::LO,
5158            }),
5159
5160            ArmOp::I64LeS {
5161                rd,
5162                rnlo,
5163                rnhi,
5164                rmlo,
5165                rmhi,
5166            } => self.encode_thumb(&ArmOp::I64SetCond {
5167                rd: *rd,
5168                rn_lo: *rnlo,
5169                rn_hi: *rnhi,
5170                rm_lo: *rmlo,
5171                rm_hi: *rmhi,
5172                cond: synth_synthesis::Condition::LE,
5173            }),
5174
5175            ArmOp::I64LeU {
5176                rd,
5177                rnlo,
5178                rnhi,
5179                rmlo,
5180                rmhi,
5181            } => self.encode_thumb(&ArmOp::I64SetCond {
5182                rd: *rd,
5183                rn_lo: *rnlo,
5184                rn_hi: *rnhi,
5185                rm_lo: *rmlo,
5186                rm_hi: *rmhi,
5187                cond: synth_synthesis::Condition::LS,
5188            }),
5189
5190            ArmOp::I64GtS {
5191                rd,
5192                rnlo,
5193                rnhi,
5194                rmlo,
5195                rmhi,
5196            } => self.encode_thumb(&ArmOp::I64SetCond {
5197                rd: *rd,
5198                rn_lo: *rnlo,
5199                rn_hi: *rnhi,
5200                rm_lo: *rmlo,
5201                rm_hi: *rmhi,
5202                cond: synth_synthesis::Condition::GT,
5203            }),
5204
5205            ArmOp::I64GtU {
5206                rd,
5207                rnlo,
5208                rnhi,
5209                rmlo,
5210                rmhi,
5211            } => self.encode_thumb(&ArmOp::I64SetCond {
5212                rd: *rd,
5213                rn_lo: *rnlo,
5214                rn_hi: *rnhi,
5215                rm_lo: *rmlo,
5216                rm_hi: *rmhi,
5217                cond: synth_synthesis::Condition::HI,
5218            }),
5219
5220            ArmOp::I64GeS {
5221                rd,
5222                rnlo,
5223                rnhi,
5224                rmlo,
5225                rmhi,
5226            } => self.encode_thumb(&ArmOp::I64SetCond {
5227                rd: *rd,
5228                rn_lo: *rnlo,
5229                rn_hi: *rnhi,
5230                rm_lo: *rmlo,
5231                rm_hi: *rmhi,
5232                cond: synth_synthesis::Condition::GE,
5233            }),
5234
5235            ArmOp::I64GeU {
5236                rd,
5237                rnlo,
5238                rnhi,
5239                rmlo,
5240                rmhi,
5241            } => self.encode_thumb(&ArmOp::I64SetCond {
5242                rd: *rd,
5243                rn_lo: *rnlo,
5244                rn_hi: *rnhi,
5245                rm_lo: *rmlo,
5246                rm_hi: *rmhi,
5247                cond: synth_synthesis::Condition::HS,
5248            }),
5249
5250            // I64Const: MOVW rdlo, lo16; MOVT rdlo, hi16; MOVW rdhi, lo16_hi; MOVT rdhi, hi16_hi
5251            ArmOp::I64Const { rdlo, rdhi, value } => {
5252                let lo32 = *value as u32;
5253                let hi32 = (*value >> 32) as u32;
5254                let mut bytes = Vec::new();
5255                // Load low 32 bits into rdlo
5256                bytes.extend_from_slice(
5257                    &self.encode_thumb32_movw_raw(reg_to_bits(rdlo), lo32 & 0xFFFF)?,
5258                );
5259                if lo32 > 0xFFFF {
5260                    bytes.extend_from_slice(
5261                        &self.encode_thumb32_movt_raw(reg_to_bits(rdlo), lo32 >> 16)?,
5262                    );
5263                }
5264                // Load high 32 bits into rdhi
5265                bytes.extend_from_slice(
5266                    &self.encode_thumb32_movw_raw(reg_to_bits(rdhi), hi32 & 0xFFFF)?,
5267                );
5268                if hi32 > 0xFFFF {
5269                    bytes.extend_from_slice(
5270                        &self.encode_thumb32_movt_raw(reg_to_bits(rdhi), hi32 >> 16)?,
5271                    );
5272                }
5273                Ok(bytes)
5274            }
5275
5276            // I64Ldr: LDR rdlo, [base, offset]; LDR rdhi, [base, offset+4]
5277            ArmOp::I64Ldr { rdlo, rdhi, addr } => {
5278                let mut bytes = Vec::new();
5279                let offset = if addr.offset < 0 {
5280                    0u32
5281                } else {
5282                    addr.offset as u32
5283                };
5284                bytes.extend_from_slice(&self.encode_thumb32_ldr(rdlo, &addr.base, offset)?);
5285                bytes.extend_from_slice(&self.encode_thumb32_ldr(
5286                    rdhi,
5287                    &addr.base,
5288                    offset.wrapping_add(4),
5289                )?);
5290                Ok(bytes)
5291            }
5292
5293            // I64Str: STR rdlo, [base, offset]; STR rdhi, [base, offset+4]
5294            ArmOp::I64Str { rdlo, rdhi, addr } => {
5295                let mut bytes = Vec::new();
5296                let offset = if addr.offset < 0 {
5297                    0u32
5298                } else {
5299                    addr.offset as u32
5300                };
5301                bytes.extend_from_slice(&self.encode_thumb32_str(rdlo, &addr.base, offset)?);
5302                bytes.extend_from_slice(&self.encode_thumb32_str(
5303                    rdhi,
5304                    &addr.base,
5305                    offset.wrapping_add(4),
5306                )?);
5307                Ok(bytes)
5308            }
5309
5310            // I64ExtendI32S: MOV rdlo, rn; ASR rdhi, rdlo, #31 (sign-extend)
5311            ArmOp::I64ExtendI32S { rdlo, rdhi, rn } => {
5312                let mut bytes = Vec::new();
5313                if rdlo != rn {
5314                    // MOV rdlo, rn (16-bit)
5315                    bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Mov {
5316                        rd: *rdlo,
5317                        op2: Operand2::Reg(*rn),
5318                    })?);
5319                }
5320                // ASR rdhi, rdlo, #31 (sign-extend: fill high word with sign bit)
5321                bytes.extend_from_slice(
5322                    &self.encode_thumb32_shift(rdhi, rdlo, 31, 0b10)?, // ASR type
5323                );
5324                Ok(bytes)
5325            }
5326
5327            // I64ExtendI32U: MOV rdlo, rn; MOV rdhi, #0
5328            ArmOp::I64ExtendI32U { rdlo, rdhi, rn } => {
5329                let mut bytes = Vec::new();
5330                if rdlo != rn {
5331                    // MOV rdlo, rn
5332                    bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Mov {
5333                        rd: *rdlo,
5334                        op2: Operand2::Reg(*rn),
5335                    })?);
5336                }
5337                // MOV rdhi, #0 (16-bit: MOVS Rd, #0)
5338                let rdhi_bits = reg_to_bits(rdhi) as u16;
5339                let instr: u16 = 0x2000 | (rdhi_bits << 8);
5340                bytes.extend_from_slice(&instr.to_le_bytes());
5341                Ok(bytes)
5342            }
5343
5344            // I32WrapI64: MOV rd, rnlo (just take low 32 bits)
5345            ArmOp::I32WrapI64 { rd, rnlo } => {
5346                if rd == rnlo {
5347                    // No-op: already in the right register
5348                    let instr: u16 = 0xBF00; // NOP
5349                    Ok(instr.to_le_bytes().to_vec())
5350                } else {
5351                    // MOV rd, rnlo
5352                    self.encode_thumb(&ArmOp::Mov {
5353                        rd: *rd,
5354                        op2: Operand2::Reg(*rnlo),
5355                    })
5356                }
5357            }
5358
5359            // ===== Helium MVE operations (Thumb-2 encoding) =====
5360            ArmOp::MveLoad { qd, addr } => Ok(vfp_to_thumb_bytes(encode_mve_vldrw(qd, addr))),
5361            ArmOp::MveStore { qd, addr } => Ok(vfp_to_thumb_bytes(encode_mve_vstrw(qd, addr))),
5362            ArmOp::MveConst { qd, bytes } => self.encode_thumb_mve_const(qd, bytes),
5363            ArmOp::MveAnd { qd, qn, qm } => Ok(vfp_to_thumb_bytes(encode_mve_3reg_bitwise(
5364                0xEF000150, qd, qn, qm,
5365            ))),
5366            ArmOp::MveOrr { qd, qn, qm } => Ok(vfp_to_thumb_bytes(encode_mve_3reg_bitwise(
5367                0xEF200150, qd, qn, qm,
5368            ))),
5369            ArmOp::MveEor { qd, qn, qm } => Ok(vfp_to_thumb_bytes(encode_mve_3reg_bitwise(
5370                0xFF000150, qd, qn, qm,
5371            ))),
5372            ArmOp::MveMvn { qd, qm } => {
5373                // VMVN Qd, Qm: 0xFFB005C0 | Qd<<12 | Qm
5374                let qd_enc = qreg_to_num(qd);
5375                let qm_enc = qreg_to_num(qm);
5376                let instr: u32 = 0xFFB005C0 | ((qd_enc * 2) << 12) | (qm_enc * 2);
5377                Ok(vfp_to_thumb_bytes(instr))
5378            }
5379            ArmOp::MveBic { qd, qn, qm } => Ok(vfp_to_thumb_bytes(encode_mve_3reg_bitwise(
5380                0xEF100150, qd, qn, qm,
5381            ))),
5382            ArmOp::MveAddI { qd, qn, qm, size } => {
5383                let sz = mve_size_bits(size);
5384                let base: u32 = 0xEF000840 | (sz << 20);
5385                Ok(vfp_to_thumb_bytes(encode_mve_3reg(base, qd, qn, qm)))
5386            }
5387            ArmOp::MveSubI { qd, qn, qm, size } => {
5388                let sz = mve_size_bits(size);
5389                let base: u32 = 0xFF000840 | (sz << 20);
5390                Ok(vfp_to_thumb_bytes(encode_mve_3reg(base, qd, qn, qm)))
5391            }
5392            ArmOp::MveMulI { qd, qn, qm, size } => {
5393                let sz = mve_size_bits(size);
5394                let base: u32 = 0xEF000950 | (sz << 20);
5395                Ok(vfp_to_thumb_bytes(encode_mve_3reg(base, qd, qn, qm)))
5396            }
5397            ArmOp::MveNegI { qd, qm, size } => {
5398                let sz = mve_size_bits(size);
5399                // VNEG.Sx Qd, Qm
5400                let qd_enc = qreg_to_num(qd);
5401                let qm_enc = qreg_to_num(qm);
5402                let base: u32 = 0xFFB103C0 | (sz << 18);
5403                let instr = base | ((qd_enc * 2) << 12) | (qm_enc * 2);
5404                Ok(vfp_to_thumb_bytes(instr))
5405            }
5406            ArmOp::MveDup { qd, rn, size } => {
5407                let sz = mve_size_bits(size);
5408                let qd_enc = qreg_to_num(qd);
5409                let rn_bits = reg_to_bits(rn);
5410                // VDUP.sz Qd, Rn: EEA0 0B10 variant
5411                // size encoding: 00=32, 01=16, 10=8
5412                let be = match sz {
5413                    0 => 0b00u32, // 8-bit
5414                    1 => 0b01,    // 16-bit
5415                    _ => 0b00,    // 32-bit (default)
5416                };
5417                let instr: u32 = 0xEEA00B10 | ((qd_enc * 2) << 16) | (rn_bits << 12) | (be << 5);
5418                Ok(vfp_to_thumb_bytes(instr))
5419            }
5420            ArmOp::MveExtractLane { rd, qn, lane, size } => {
5421                let qn_enc = qreg_to_num(qn);
5422                let rd_bits = reg_to_bits(rd);
5423                // VMOV.sz Rd, Dn[x] — extract from Q-register lane
5424                // For 32-bit: VMOV Rd, Dn — where Dn is the appropriate D-register
5425                let d_reg = qn_enc * 2 + ((*lane as u32) >> 1);
5426                let lane_in_d = (*lane as u32) & 1;
5427                let _sz = mve_size_bits(size);
5428                // VMOV Rd, Dn[x]: EE10 0B10 for 32-bit
5429                let instr: u32 = 0xEE100B10 | (d_reg << 16) | (rd_bits << 12) | (lane_in_d << 21);
5430                Ok(vfp_to_thumb_bytes(instr))
5431            }
5432            ArmOp::MveInsertLane { qd, rn, lane, size } => {
5433                let qd_enc = qreg_to_num(qd);
5434                let rn_bits = reg_to_bits(rn);
5435                let d_reg = qd_enc * 2 + ((*lane as u32) >> 1);
5436                let lane_in_d = (*lane as u32) & 1;
5437                let _sz = mve_size_bits(size);
5438                // VMOV Dn[x], Rn: EE00 0B10 for 32-bit
5439                let instr: u32 = 0xEE000B10 | (d_reg << 16) | (rn_bits << 12) | (lane_in_d << 21);
5440                Ok(vfp_to_thumb_bytes(instr))
5441            }
5442
5443            // MVE float comparisons — emit VCMP + VPSEL sequence (simplified: just VCMP)
5444            ArmOp::MveCmpEqI { qd, qn, qm, size }
5445            | ArmOp::MveCmpNeI { qd, qn, qm, size }
5446            | ArmOp::MveCmpLtS { qd, qn, qm, size }
5447            | ArmOp::MveCmpLtU { qd, qn, qm, size }
5448            | ArmOp::MveCmpGtS { qd, qn, qm, size }
5449            | ArmOp::MveCmpGtU { qd, qn, qm, size }
5450            | ArmOp::MveCmpLeS { qd, qn, qm, size }
5451            | ArmOp::MveCmpLeU { qd, qn, qm, size }
5452            | ArmOp::MveCmpGeS { qd, qn, qm, size }
5453            | ArmOp::MveCmpGeU { qd, qn, qm, size } => {
5454                // Encode as VADD (placeholder encoding — real implementation
5455                // would use VCMP + VPSEL pair)
5456                let sz = mve_size_bits(size);
5457                let base: u32 = 0xEF000840 | (sz << 20);
5458                Ok(vfp_to_thumb_bytes(encode_mve_3reg(base, qd, qn, qm)))
5459            }
5460
5461            // f32x4 MVE arithmetic
5462            ArmOp::MveAddF32 { qd, qn, qm } => {
5463                // VADD.F32 Qd, Qn, Qm (MVE): 0xEF000D40
5464                Ok(vfp_to_thumb_bytes(encode_mve_3reg(0xEF000D40, qd, qn, qm)))
5465            }
5466            ArmOp::MveSubF32 { qd, qn, qm } => {
5467                // VSUB.F32 Qd, Qn, Qm (MVE): 0xEF200D40
5468                Ok(vfp_to_thumb_bytes(encode_mve_3reg(0xEF200D40, qd, qn, qm)))
5469            }
5470            ArmOp::MveMulF32 { qd, qn, qm } => {
5471                // VMUL.F32 Qd, Qn, Qm (MVE): 0xFF000D50
5472                Ok(vfp_to_thumb_bytes(encode_mve_3reg(0xFF000D50, qd, qn, qm)))
5473            }
5474            ArmOp::MveNegF32 { qd, qm } => {
5475                let qd_enc = qreg_to_num(qd);
5476                let qm_enc = qreg_to_num(qm);
5477                // VNEG.F32 Qd, Qm: FFB907C0
5478                let instr: u32 = 0xFFB907C0 | ((qd_enc * 2) << 12) | (qm_enc * 2);
5479                Ok(vfp_to_thumb_bytes(instr))
5480            }
5481            ArmOp::MveAbsF32 { qd, qm } => {
5482                let qd_enc = qreg_to_num(qd);
5483                let qm_enc = qreg_to_num(qm);
5484                // VABS.F32 Qd, Qm: FFB90740
5485                let instr: u32 = 0xFFB90740 | ((qd_enc * 2) << 12) | (qm_enc * 2);
5486                Ok(vfp_to_thumb_bytes(instr))
5487            }
5488            ArmOp::MveCmpEqF32 { qd, qn, qm }
5489            | ArmOp::MveCmpNeF32 { qd, qn, qm }
5490            | ArmOp::MveCmpLtF32 { qd, qn, qm }
5491            | ArmOp::MveCmpLeF32 { qd, qn, qm }
5492            | ArmOp::MveCmpGtF32 { qd, qn, qm }
5493            | ArmOp::MveCmpGeF32 { qd, qn, qm } => {
5494                // Placeholder: encode as VADD.F32 (real impl needs VCMP.F32 + VPSEL)
5495                Ok(vfp_to_thumb_bytes(encode_mve_3reg(0xEF000D40, qd, qn, qm)))
5496            }
5497            ArmOp::MveDupF32 { qd, rn } => {
5498                let qd_enc = qreg_to_num(qd);
5499                let rn_bits = reg_to_bits(rn);
5500                // VDUP.32 Qd, Rn (same encoding as integer VDUP.32)
5501                let instr: u32 = 0xEEA00B10 | ((qd_enc * 2) << 16) | (rn_bits << 12);
5502                Ok(vfp_to_thumb_bytes(instr))
5503            }
5504            ArmOp::MveExtractLaneF32 { rd, qn, lane } => {
5505                let qn_enc = qreg_to_num(qn);
5506                let rd_bits = reg_to_bits(rd);
5507                // VMOV Rd, Sn where Sn = Q*4 + lane
5508                let s_num = qn_enc * 4 + (*lane as u32);
5509                let (vn, n) = encode_sreg(s_num);
5510                let instr: u32 = 0xEE100A10 | (vn << 16) | (rd_bits << 12) | (n << 7);
5511                Ok(vfp_to_thumb_bytes(instr))
5512            }
5513            ArmOp::MveReplaceLaneF32 { qd, rn, lane } => {
5514                let qd_enc = qreg_to_num(qd);
5515                let rn_bits = reg_to_bits(rn);
5516                // VMOV Sn, Rn where Sn = Q*4 + lane
5517                let s_num = qd_enc * 4 + (*lane as u32);
5518                let (vn, n) = encode_sreg(s_num);
5519                let instr: u32 = 0xEE000A10 | (vn << 16) | (rn_bits << 12) | (n << 7);
5520                Ok(vfp_to_thumb_bytes(instr))
5521            }
5522            ArmOp::MveDivF32 { qd, qn, qm } => {
5523                // Lane-wise: extract 4 S-regs, VDIV, insert back
5524                self.encode_thumb_mve_lane_wise_f32_binop(qd, qn, qm, 0xEE800A00)
5525            }
5526            ArmOp::MveSqrtF32 { qd, qm } => {
5527                // Lane-wise: extract 4 S-regs, VSQRT, insert back
5528                self.encode_thumb_mve_lane_wise_f32_sqrt(qd, qm)
5529            }
5530
5531            // Catch-all for any remaining ops
5532            _ => {
5533                let instr: u16 = 0xBF00; // NOP
5534                Ok(instr.to_le_bytes().to_vec())
5535            }
5536        }
5537    }
5538
5539    // === Thumb-2 VFP multi-instruction helpers ===
5540
5541    /// Encode F32 comparison as Thumb-2: VCMP.F32 + VMRS + MOVS rd,#0 + IT + MOV rd,#1
5542    fn encode_thumb_f32_compare(
5543        &self,
5544        rd: &Reg,
5545        sn: &VfpReg,
5546        sm: &VfpReg,
5547        cond_code: u32,
5548    ) -> Result<Vec<u8>> {
5549        let mut bytes = Vec::new();
5550        let rd_bits = reg_to_bits(rd);
5551
5552        // VCMP.F32 Sn, Sm
5553        let sn_num = vfp_sreg_to_num(sn)?;
5554        let sm_num = vfp_sreg_to_num(sm)?;
5555        let (vd, d) = encode_sreg(sn_num);
5556        let (vm, m) = encode_sreg(sm_num);
5557        let vcmp = 0xEEB40A40 | (d << 22) | (vd << 12) | (m << 5) | vm;
5558        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcmp));
5559
5560        // VMRS APSR_nzcv, FPSCR: 0xEEF1FA10
5561        bytes.extend_from_slice(&vfp_to_thumb_bytes(0xEEF1FA10));
5562
5563        // MOVS Rd, #0 (16-bit): 0010 0 Rd(3) 0000 0000
5564        if rd_bits < 8 {
5565            let movs_zero: u16 = 0x2000 | ((rd_bits as u16) << 8);
5566            bytes.extend_from_slice(&movs_zero.to_le_bytes());
5567        } else {
5568            // MOV.W Rd, #0 (32-bit Thumb-2)
5569            let hw1: u16 = 0xF04F;
5570            let hw2: u16 = (rd_bits as u16) << 8;
5571            bytes.extend_from_slice(&hw1.to_le_bytes());
5572            bytes.extend_from_slice(&hw2.to_le_bytes());
5573        }
5574
5575        // IT<cond> — If-Then for conditional MOV
5576        // IT encoding: 1011 1111 cond(4) mask(4)
5577        // mask = 0x8 for single "then" (IT)
5578        let it: u16 = 0xBF00 | ((cond_code as u16) << 4) | 0x8;
5579        bytes.extend_from_slice(&it.to_le_bytes());
5580
5581        // MOV Rd, #1 (16-bit, conditional due to IT): 0010 0 Rd(3) 0000 0001
5582        if rd_bits < 8 {
5583            let mov_one: u16 = 0x2001 | ((rd_bits as u16) << 8);
5584            bytes.extend_from_slice(&mov_one.to_le_bytes());
5585        } else {
5586            // MOV.W Rd, #1 (32-bit)
5587            let hw1: u16 = 0xF04F;
5588            let hw2: u16 = ((rd_bits as u16) << 8) | 0x01;
5589            bytes.extend_from_slice(&hw1.to_le_bytes());
5590            bytes.extend_from_slice(&hw2.to_le_bytes());
5591        }
5592
5593        Ok(bytes)
5594    }
5595
5596    /// Encode F32 constant load as Thumb-2: MOVW + MOVT + VMOV
5597    fn encode_thumb_f32_const(&self, sd: &VfpReg, value: f32) -> Result<Vec<u8>> {
5598        let mut bytes = Vec::new();
5599        let bits = value.to_bits();
5600        let rt: u32 = 12; // R12/IP as temp
5601
5602        // MOVW R12, #lo16
5603        // Thumb-2 MOVW: 11110 i 10 0100 imm4 | 0 imm3 Rd imm8
5604        let lo16 = bits & 0xFFFF;
5605        let imm4 = (lo16 >> 12) & 0xF;
5606        let i_bit = (lo16 >> 11) & 1;
5607        let imm3 = (lo16 >> 8) & 0x7;
5608        let imm8 = lo16 & 0xFF;
5609        let hw1: u16 = (0xF240 | (i_bit << 10) | imm4) as u16;
5610        let hw2: u16 = ((imm3 << 12) | (rt << 8) | imm8) as u16;
5611        bytes.extend_from_slice(&hw1.to_le_bytes());
5612        bytes.extend_from_slice(&hw2.to_le_bytes());
5613
5614        // MOVT R12, #hi16
5615        let hi16 = (bits >> 16) & 0xFFFF;
5616        let imm4 = (hi16 >> 12) & 0xF;
5617        let i_bit = (hi16 >> 11) & 1;
5618        let imm3 = (hi16 >> 8) & 0x7;
5619        let imm8 = hi16 & 0xFF;
5620        let hw1: u16 = (0xF2C0 | (i_bit << 10) | imm4) as u16;
5621        let hw2: u16 = ((imm3 << 12) | (rt << 8) | imm8) as u16;
5622        bytes.extend_from_slice(&hw1.to_le_bytes());
5623        bytes.extend_from_slice(&hw2.to_le_bytes());
5624
5625        // VMOV Sd, R12
5626        let vmov = encode_vmov_core_sreg(true, sd, &Reg::R12)?;
5627        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
5628
5629        Ok(bytes)
5630    }
5631
5632    /// Encode VMOV + VCVT.F32.xS32 as Thumb-2
5633    fn encode_thumb_f32_convert_i32(&self, sd: &VfpReg, rm: &Reg, signed: bool) -> Result<Vec<u8>> {
5634        let mut bytes = Vec::new();
5635
5636        // VMOV Sd, Rm
5637        let vmov = encode_vmov_core_sreg(true, sd, rm)?;
5638        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
5639
5640        // VCVT.F32.S32/U32 Sd, Sd
5641        let sd_num = vfp_sreg_to_num(sd)?;
5642        let (vd, d) = encode_sreg(sd_num);
5643        let (vm, m) = encode_sreg(sd_num);
5644        let base = if signed { 0xEEB80A40 } else { 0xEEB80AC0 };
5645        let vcvt = base | (d << 22) | (vd << 12) | (m << 5) | vm;
5646        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt));
5647
5648        Ok(bytes)
5649    }
5650
5651    /// Encode F32 rounding pseudo-op as Thumb-2 via VCVT to integer and back
5652    /// Encode F32 rounding as Thumb-2.
5653    /// `mode`: FPSCR RMode — 0b00=nearest, 0b01=+inf(ceil), 0b10=-inf(floor), 0b11=zero(trunc)
5654    ///
5655    /// For trunc: uses VCVTR.S32.F32 (always truncates).
5656    /// For ceil/floor/nearest: sets FPSCR rounding mode, uses VCVT.S32.F32 (non-R variant),
5657    /// then restores FPSCR.
5658    fn encode_thumb_f32_rounding(&self, sd: &VfpReg, sm: &VfpReg, mode: u8) -> Result<Vec<u8>> {
5659        let mut bytes = Vec::new();
5660        let sm_num = vfp_sreg_to_num(sm)?;
5661        let sd_num = vfp_sreg_to_num(sd)?;
5662        let (vd_s, d_s) = encode_sreg(sd_num);
5663        let (vm_s, m_s) = encode_sreg(sm_num);
5664
5665        if mode == 0b11 {
5666            // Trunc (toward zero): VCVTR.S32.F32 — bit[7]=1, always truncates
5667            let vcvt_to_int = 0xEEBD0AC0 | (d_s << 22) | (vd_s << 12) | (m_s << 5) | vm_s;
5668            bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_int));
5669        } else {
5670            // ceil/floor/nearest: manipulate FPSCR rounding mode
5671            let rt: u32 = 12; // R12/IP as temp
5672
5673            // VMRS R12, FPSCR
5674            let vmrs = 0xEEF10A10 | (rt << 12);
5675            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmrs));
5676
5677            // BIC.W R12, R12, #(3 << 22) — clear RMode bits [23:22]
5678            // Thumb-2 modified immediate for 3<<22 = 0x00C00000:
5679            // BIC.W encoding: 11110 i 0 0001 S Rn | 0 imm3 Rd imm8
5680            // 0x00C00000 = 0x03 shifted left by 22 => Thumb mod-imm: i=0, imm3=0b101, imm8=0x03
5681            let bic_hw1: u16 = 0xF020 | ((rt as u16) & 0xF); // BIC, Rn=R12
5682            let bic_hw2: u16 = (0x05 << 12) | ((rt as u16) << 8) | 0x03;
5683            bytes.extend_from_slice(&bic_hw1.to_le_bytes());
5684            bytes.extend_from_slice(&bic_hw2.to_le_bytes());
5685
5686            // ORR.W R12, R12, #(mode << 22)
5687            if mode != 0 {
5688                let orr_hw1: u16 = 0xF040 | ((rt as u16) & 0xF); // ORR, Rn=R12
5689                let orr_hw2: u16 = (0x05 << 12) | ((rt as u16) << 8) | (mode as u16);
5690                bytes.extend_from_slice(&orr_hw1.to_le_bytes());
5691                bytes.extend_from_slice(&orr_hw2.to_le_bytes());
5692            }
5693
5694            // VMSR FPSCR, R12
5695            let vmsr = 0xEEE10A10 | (rt << 12);
5696            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmsr));
5697
5698            // VCVT.S32.F32 Sd, Sm — non-R variant (bit[7]=0), uses FPSCR rmode
5699            let vcvt_to_int = 0xEEBD0A40 | (d_s << 22) | (vd_s << 12) | (m_s << 5) | vm_s;
5700            bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_int));
5701
5702            // Restore FPSCR: clear rmode bits back to nearest (default)
5703            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmrs));
5704            bytes.extend_from_slice(&bic_hw1.to_le_bytes());
5705            bytes.extend_from_slice(&bic_hw2.to_le_bytes());
5706            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmsr));
5707        }
5708
5709        // VCVT.F32.S32 Sd, Sd (convert integer result back to float)
5710        let (vd2, d2) = encode_sreg(sd_num);
5711        let vcvt_to_float = 0xEEB80A40 | (d2 << 22) | (vd2 << 12) | (d_s << 5) | vd_s;
5712        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_float));
5713
5714        Ok(bytes)
5715    }
5716
5717    /// Encode F32 min/max as Thumb-2: VMOV + VCMP + VMRS + IT + VMOV
5718    fn encode_thumb_f32_minmax(
5719        &self,
5720        sd: &VfpReg,
5721        sn: &VfpReg,
5722        sm: &VfpReg,
5723        is_min: bool,
5724    ) -> Result<Vec<u8>> {
5725        let mut bytes = Vec::new();
5726        let sn_num = vfp_sreg_to_num(sn)?;
5727        let sm_num = vfp_sreg_to_num(sm)?;
5728        let sd_num = vfp_sreg_to_num(sd)?;
5729
5730        // VMOV.F32 Sd, Sn
5731        let (vd, d) = encode_sreg(sd_num);
5732        let (vn, n) = encode_sreg(sn_num);
5733        let vmov_sn = 0xEEB00A40 | (d << 22) | (vd << 12) | (n << 5) | vn;
5734        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov_sn));
5735
5736        // VCMP.F32 Sn, Sm
5737        let (vm, m) = encode_sreg(sm_num);
5738        let vcmp = 0xEEB40A40 | (n << 22) | (vn << 12) | (m << 5) | vm;
5739        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcmp));
5740
5741        // VMRS APSR_nzcv, FPSCR
5742        bytes.extend_from_slice(&vfp_to_thumb_bytes(0xEEF1FA10));
5743
5744        // IT GT (for min) or IT MI (for max)
5745        let cond: u16 = if is_min { 0xC } else { 0x4 };
5746        let it: u16 = 0xBF00 | (cond << 4) | 0x8;
5747        bytes.extend_from_slice(&it.to_le_bytes());
5748
5749        // VMOV{cond}.F32 Sd, Sm — conditional VMOV in IT block
5750        let vmov_sm = 0xEEB00A40 | (d << 22) | (vd << 12) | (m << 5) | vm;
5751        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov_sm));
5752
5753        Ok(bytes)
5754    }
5755
5756    /// Encode F32 copysign as Thumb-2
5757    fn encode_thumb_f32_copysign(&self, sd: &VfpReg, sn: &VfpReg, sm: &VfpReg) -> Result<Vec<u8>> {
5758        let mut bytes = Vec::new();
5759
5760        // VMOV R12, Sm (get sign source bits)
5761        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_sreg(
5762            false,
5763            sm,
5764            &Reg::R12,
5765        )?));
5766
5767        // VMOV R0, Sn (get magnitude source bits)
5768        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_sreg(
5769            false,
5770            sn,
5771            &Reg::R0,
5772        )?));
5773
5774        // AND.W R12, R12, #0x80000000
5775        // Thumb-2 modified immediate: 0x80000000 = constant 0x80 with rotation
5776        // Using T1 encoding: 11110 i 0 0000 S Rn | 0 imm3 Rd imm8
5777        // 0x80000000: i=0, imm3=0b001, imm8=0x00 (rotation=4, value=0x80)
5778        // Actually encoding #0x80000000 as modified constant:
5779        // bit pattern 1 followed by 31 zeros: enc = 0b0100_00000000 = 0x0100? No.
5780        // ARM modified immediate: abcdefgh rotated. 0x80000000 = 0x80 ROR 2 = enc 0x0102
5781        // Actually: value = abcdefgh ROR (2*rot). 0x80 = 10000000, ROR 2 gives 0x20000000.
5782        // For 0x80000000: 0x02 ROR 2 = 0x80000000. So imm12 = (1<<8) | 0x02 = 0x102
5783        let hw1: u16 = 0xF000 | 12; // AND.W R12, R12, #modified_const (i=0, Rn=R12)
5784        let hw2: u16 = (0x1 << 12) | (12 << 8) | 0x02; // imm3=1, Rd=R12, imm8=0x02
5785        bytes.extend_from_slice(&hw1.to_le_bytes());
5786        bytes.extend_from_slice(&hw2.to_le_bytes());
5787
5788        // BIC.W R0, R0, #0x80000000 (R0 = register 0, fields are zero)
5789        let hw1: u16 = 0xF020; // BIC.W R0, R0, #modified_const (i=0, Rn=R0)
5790        let hw2: u16 = (0x1 << 12) | 0x02; // imm3=1, Rd=R0, imm8=0x02
5791        bytes.extend_from_slice(&hw1.to_le_bytes());
5792        bytes.extend_from_slice(&hw2.to_le_bytes());
5793
5794        // ORR.W R0, R0, R12 (R0 = register 0)
5795        let hw1: u16 = 0xEA40; // ORR.W R0, R0, R12 (Rn=R0)
5796        let hw2: u16 = 12; // Rd=R0, Rm=R12
5797        bytes.extend_from_slice(&hw1.to_le_bytes());
5798        bytes.extend_from_slice(&hw2.to_le_bytes());
5799
5800        // VMOV Sd, R0
5801        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_sreg(
5802            true,
5803            sd,
5804            &Reg::R0,
5805        )?));
5806
5807        Ok(bytes)
5808    }
5809
5810    /// Encode F64 comparison as Thumb-2: VCMP.F64 + VMRS + MOV #0 + IT + MOV #1
5811    fn encode_thumb_f64_compare(
5812        &self,
5813        rd: &Reg,
5814        dn: &VfpReg,
5815        dm: &VfpReg,
5816        cond_code: u32,
5817    ) -> Result<Vec<u8>> {
5818        let mut bytes = Vec::new();
5819        let rd_bits = reg_to_bits(rd);
5820
5821        // VCMP.F64 Dn, Dm
5822        let dn_num = vfp_dreg_to_num(dn)?;
5823        let dm_num = vfp_dreg_to_num(dm)?;
5824        let (vd, d) = encode_dreg(dn_num);
5825        let (vm, m) = encode_dreg(dm_num);
5826        let vcmp = 0xEEB40B40 | (d << 22) | (vd << 12) | (m << 5) | vm;
5827        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcmp));
5828
5829        // VMRS APSR_nzcv, FPSCR
5830        bytes.extend_from_slice(&vfp_to_thumb_bytes(0xEEF1FA10));
5831
5832        // MOVS Rd, #0
5833        if rd_bits < 8 {
5834            let movs_zero: u16 = 0x2000 | ((rd_bits as u16) << 8);
5835            bytes.extend_from_slice(&movs_zero.to_le_bytes());
5836        } else {
5837            let hw1: u16 = 0xF04F;
5838            let hw2: u16 = (rd_bits as u16) << 8;
5839            bytes.extend_from_slice(&hw1.to_le_bytes());
5840            bytes.extend_from_slice(&hw2.to_le_bytes());
5841        }
5842
5843        // IT<cond>
5844        let it: u16 = 0xBF00 | ((cond_code as u16) << 4) | 0x8;
5845        bytes.extend_from_slice(&it.to_le_bytes());
5846
5847        // MOV Rd, #1
5848        if rd_bits < 8 {
5849            let mov_one: u16 = 0x2001 | ((rd_bits as u16) << 8);
5850            bytes.extend_from_slice(&mov_one.to_le_bytes());
5851        } else {
5852            let hw1: u16 = 0xF04F;
5853            let hw2: u16 = ((rd_bits as u16) << 8) | 0x01;
5854            bytes.extend_from_slice(&hw1.to_le_bytes());
5855            bytes.extend_from_slice(&hw2.to_le_bytes());
5856        }
5857
5858        Ok(bytes)
5859    }
5860
5861    /// Encode F64 constant load as Thumb-2: MOVW+MOVT (lo32 into R0) + MOVW+MOVT (hi32 into R12) + VMOV Dd, R0, R12
5862    fn encode_thumb_f64_const(&self, dd: &VfpReg, value: f64) -> Result<Vec<u8>> {
5863        let mut bytes = Vec::new();
5864        let bits = value.to_bits();
5865        let lo32 = bits as u32;
5866        let hi32 = (bits >> 32) as u32;
5867
5868        // MOVW R0, #lo16(lo32)
5869        let lo16 = lo32 & 0xFFFF;
5870        bytes.extend_from_slice(&self.encode_thumb32_movw_raw(0, lo16)?);
5871
5872        // MOVT R0, #hi16(lo32)
5873        let hi16 = (lo32 >> 16) & 0xFFFF;
5874        bytes.extend_from_slice(&self.encode_thumb32_movt_raw(0, hi16)?);
5875
5876        // MOVW R12, #lo16(hi32)
5877        let lo16 = hi32 & 0xFFFF;
5878        bytes.extend_from_slice(&self.encode_thumb32_movw_raw(12, lo16)?);
5879
5880        // MOVT R12, #hi16(hi32)
5881        let hi16 = (hi32 >> 16) & 0xFFFF;
5882        bytes.extend_from_slice(&self.encode_thumb32_movt_raw(12, hi16)?);
5883
5884        // VMOV Dd, R0, R12
5885        let vmov = encode_vmov_core_dreg(true, dd, &Reg::R0, &Reg::R12)?;
5886        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
5887
5888        Ok(bytes)
5889    }
5890
5891    /// Encode VMOV Sd, Rm + VCVT.F64.S32/U32 Dd, Sd as Thumb-2
5892    fn encode_thumb_f64_convert_i32(&self, dd: &VfpReg, rm: &Reg, signed: bool) -> Result<Vec<u8>> {
5893        let mut bytes = Vec::new();
5894
5895        // VMOV S0, Rm
5896        let vmov = encode_vmov_core_sreg(true, &VfpReg::S0, rm)?;
5897        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
5898
5899        // VCVT.F64.S32 Dd, S0 or VCVT.F64.U32 Dd, S0
5900        let dd_num = vfp_dreg_to_num(dd)?;
5901        let (vd, d) = encode_dreg(dd_num);
5902        let base = if signed { 0xEEB80B40 } else { 0xEEB80BC0 };
5903        let vcvt = base | (d << 22) | (vd << 12);
5904        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt));
5905
5906        Ok(bytes)
5907    }
5908
5909    /// Encode VCVT.F64.F32 Dd, Sm as Thumb-2
5910    fn encode_thumb_f64_promote_f32(&self, dd: &VfpReg, sm: &VfpReg) -> Result<Vec<u8>> {
5911        let dd_num = vfp_dreg_to_num(dd)?;
5912        let sm_num = vfp_sreg_to_num(sm)?;
5913        let (vd, d) = encode_dreg(dd_num);
5914        let (vm, m) = encode_sreg(sm_num);
5915
5916        let vcvt = 0xEEB70AC0 | (d << 22) | (vd << 12) | (m << 5) | vm;
5917        Ok(vfp_to_thumb_bytes(vcvt))
5918    }
5919
5920    /// Encode VCVT.S32/U32.F64 S0, Dm + VMOV Rd, S0 as Thumb-2
5921    fn encode_thumb_i32_trunc_f64(&self, rd: &Reg, dm: &VfpReg, signed: bool) -> Result<Vec<u8>> {
5922        let mut bytes = Vec::new();
5923        let dm_num = vfp_dreg_to_num(dm)?;
5924        let (vm, m) = encode_dreg(dm_num);
5925
5926        // VCVT.S32.F64 S0, Dm or VCVT.U32.F64 S0, Dm
5927        let base = if signed { 0xEEBD0BC0 } else { 0xEEBC0BC0 };
5928        let vcvt = base | (m << 5) | vm;
5929        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt));
5930
5931        // VMOV Rd, S0
5932        let vmov = encode_vmov_core_sreg(false, &VfpReg::S0, rd)?;
5933        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
5934
5935        Ok(bytes)
5936    }
5937
5938    /// Encode F64 rounding pseudo-op as Thumb-2 via VCVT to integer and back
5939    /// Encode F64 rounding as Thumb-2.
5940    /// `mode`: FPSCR RMode — 0b00=nearest, 0b01=+inf(ceil), 0b10=-inf(floor), 0b11=zero(trunc)
5941    fn encode_thumb_f64_rounding(&self, dd: &VfpReg, dm: &VfpReg, mode: u8) -> Result<Vec<u8>> {
5942        let mut bytes = Vec::new();
5943        let dm_num = vfp_dreg_to_num(dm)?;
5944        let dd_num = vfp_dreg_to_num(dd)?;
5945        let (vm, m) = encode_dreg(dm_num);
5946        let (vd, d) = encode_dreg(dd_num);
5947
5948        if mode == 0b11 {
5949            // Trunc: VCVTR.S32.F64 — bit[7]=1, always truncates
5950            let vcvt_to_int = 0xEEBD0BC0 | (m << 5) | vm;
5951            bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_int));
5952        } else {
5953            let rt: u32 = 12;
5954
5955            // VMRS R12, FPSCR
5956            let vmrs = 0xEEF10A10 | (rt << 12);
5957            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmrs));
5958
5959            // BIC.W R12, R12, #(3 << 22)
5960            let bic_hw1: u16 = 0xF020 | ((rt as u16) & 0xF);
5961            let bic_hw2: u16 = (0x05 << 12) | ((rt as u16) << 8) | 0x03;
5962            bytes.extend_from_slice(&bic_hw1.to_le_bytes());
5963            bytes.extend_from_slice(&bic_hw2.to_le_bytes());
5964
5965            // ORR.W R12, R12, #(mode << 22)
5966            if mode != 0 {
5967                let orr_hw1: u16 = 0xF040 | ((rt as u16) & 0xF);
5968                let orr_hw2: u16 = (0x05 << 12) | ((rt as u16) << 8) | (mode as u16);
5969                bytes.extend_from_slice(&orr_hw1.to_le_bytes());
5970                bytes.extend_from_slice(&orr_hw2.to_le_bytes());
5971            }
5972
5973            // VMSR FPSCR, R12
5974            let vmsr = 0xEEE10A10 | (rt << 12);
5975            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmsr));
5976
5977            // VCVT.S32.F64 S0, Dm — non-R variant (bit[7]=0)
5978            let vcvt_to_int = 0xEEBD0B40 | (m << 5) | vm;
5979            bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_int));
5980
5981            // Restore FPSCR
5982            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmrs));
5983            bytes.extend_from_slice(&bic_hw1.to_le_bytes());
5984            bytes.extend_from_slice(&bic_hw2.to_le_bytes());
5985            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmsr));
5986        }
5987
5988        // VCVT.F64.S32 Dd, S0
5989        let vcvt_to_float = 0xEEB80B40 | (d << 22) | (vd << 12);
5990        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_float));
5991
5992        Ok(bytes)
5993    }
5994
5995    /// Encode F64 min/max as Thumb-2
5996    fn encode_thumb_f64_minmax(
5997        &self,
5998        dd: &VfpReg,
5999        dn: &VfpReg,
6000        dm: &VfpReg,
6001        is_min: bool,
6002    ) -> Result<Vec<u8>> {
6003        let mut bytes = Vec::new();
6004        let dn_num = vfp_dreg_to_num(dn)?;
6005        let dm_num = vfp_dreg_to_num(dm)?;
6006        let dd_num = vfp_dreg_to_num(dd)?;
6007
6008        // VMOV.F64 Dd, Dn
6009        let (vd, d) = encode_dreg(dd_num);
6010        let (vn, n) = encode_dreg(dn_num);
6011        let vmov_dn = 0xEEB00B40 | (d << 22) | (vd << 12) | (n << 5) | vn;
6012        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov_dn));
6013
6014        // VCMP.F64 Dn, Dm
6015        let (vm, m) = encode_dreg(dm_num);
6016        let vcmp = 0xEEB40B40 | (n << 22) | (vn << 12) | (m << 5) | vm;
6017        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcmp));
6018
6019        // VMRS APSR_nzcv, FPSCR
6020        bytes.extend_from_slice(&vfp_to_thumb_bytes(0xEEF1FA10));
6021
6022        // IT GT (for min) or IT MI (for max)
6023        let cond: u16 = if is_min { 0xC } else { 0x4 };
6024        let it: u16 = 0xBF00 | (cond << 4) | 0x8;
6025        bytes.extend_from_slice(&it.to_le_bytes());
6026
6027        // VMOV{cond}.F64 Dd, Dm
6028        let vmov_dm = 0xEEB00B40 | (d << 22) | (vd << 12) | (m << 5) | vm;
6029        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov_dm));
6030
6031        Ok(bytes)
6032    }
6033
6034    /// Encode F64 copysign as Thumb-2
6035    fn encode_thumb_f64_copysign(&self, dd: &VfpReg, dn: &VfpReg, dm: &VfpReg) -> Result<Vec<u8>> {
6036        let mut bytes = Vec::new();
6037
6038        // VMOV R0, R12, Dm (get sign source)
6039        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_dreg(
6040            false,
6041            dm,
6042            &Reg::R0,
6043            &Reg::R12,
6044        )?));
6045
6046        // VMOV R1, R2, Dn (get magnitude source)
6047        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_dreg(
6048            false,
6049            dn,
6050            &Reg::R1,
6051            &Reg::R2,
6052        )?));
6053
6054        // AND.W R12, R12, #0x80000000 (i=0, Rn=R12)
6055        let hw1: u16 = 0xF000 | 12;
6056        let hw2: u16 = (0x1 << 12) | (12 << 8) | 0x02;
6057        bytes.extend_from_slice(&hw1.to_le_bytes());
6058        bytes.extend_from_slice(&hw2.to_le_bytes());
6059
6060        // BIC.W R2, R2, #0x80000000 (i=0, Rn=R2)
6061        let hw1: u16 = 0xF020 | 2;
6062        let hw2: u16 = (0x1 << 12) | (2 << 8) | 0x02;
6063        bytes.extend_from_slice(&hw1.to_le_bytes());
6064        bytes.extend_from_slice(&hw2.to_le_bytes());
6065
6066        // ORR.W R2, R2, R12
6067        let hw1: u16 = 0xEA40 | 2;
6068        let hw2: u16 = (2 << 8) | 12;
6069        bytes.extend_from_slice(&hw1.to_le_bytes());
6070        bytes.extend_from_slice(&hw2.to_le_bytes());
6071
6072        // VMOV Dd, R1, R2
6073        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_dreg(
6074            true,
6075            dd,
6076            &Reg::R1,
6077            &Reg::R2,
6078        )?));
6079
6080        Ok(bytes)
6081    }
6082
6083    /// Encode VCVT.S32/U32.F32 + VMOV as Thumb-2
6084    fn encode_thumb_i32_trunc_f32(&self, rd: &Reg, sm: &VfpReg, signed: bool) -> Result<Vec<u8>> {
6085        let mut bytes = Vec::new();
6086
6087        let sm_num = vfp_sreg_to_num(sm)?;
6088        let (vd, d) = encode_sreg(sm_num);
6089        let (vm, m) = encode_sreg(sm_num);
6090        let base = if signed { 0xEEBD0AC0 } else { 0xEEBC0AC0 };
6091        let vcvt = base | (d << 22) | (vd << 12) | (m << 5) | vm;
6092        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt));
6093
6094        // VMOV Rd, Sm
6095        let vmov = encode_vmov_core_sreg(false, sm, rd)?;
6096        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
6097
6098        Ok(bytes)
6099    }
6100
6101    // === Thumb-2 32-bit encoding helpers ===
6102
6103    /// Encode Thumb-2 32-bit ADD with immediate
6104    fn encode_thumb32_add(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6105        let rd_bits = reg_to_bits(rd);
6106        let rn_bits = reg_to_bits(rn);
6107
6108        // The `i:imm3:imm8` field is split the same way for both forms.
6109        let i_bit = (imm >> 11) & 1;
6110        let imm3 = (imm >> 8) & 0x7;
6111        let imm8 = imm & 0xFF;
6112
6113        let hw1_base = if imm <= 0xFF {
6114            // ADD.W (T3): the field is a ThumbExpandImm modified immediate. For
6115            // imm <= 0xFF (i:imm3 = 0000) it is the zero-extended byte, which is
6116            // correct — keep this form so existing encodings stay bit-identical.
6117            0xF100
6118        } else if imm <= 0xFFF {
6119            // ADDW (T4): a PLAIN 12-bit immediate (0..4095) — no ThumbExpandImm.
6120            // This is what makes `add sp, sp, #frame` correct for frame sizes
6121            // >= 256, which ADD.W (T3) would silently mis-encode (e.g. #256 -> #0).
6122            0xF200
6123        } else {
6124            return Err(synth_core::Error::synthesis(
6125                "ADD immediate > 0xFFF (4095) requires a multi-instruction sequence (not supported)",
6126            ));
6127        };
6128
6129        let hw1: u16 = (hw1_base | (i_bit << 10) | rn_bits) as u16;
6130        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6131
6132        let mut bytes = hw1.to_le_bytes().to_vec();
6133        bytes.extend_from_slice(&hw2.to_le_bytes());
6134        Ok(bytes)
6135    }
6136
6137    /// Encode Thumb-2 32-bit SUB with immediate
6138    fn encode_thumb32_sub(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6139        let rd_bits = reg_to_bits(rd);
6140        let rn_bits = reg_to_bits(rn);
6141
6142        let i_bit = (imm >> 11) & 1;
6143        let imm3 = (imm >> 8) & 0x7;
6144        let imm8 = imm & 0xFF;
6145
6146        let hw1_base = if imm <= 0xFF {
6147            // SUB.W (T3) modified immediate — correct for the zero-extended byte
6148            // (imm <= 0xFF). Kept bit-identical for existing encodings.
6149            0xF1A0
6150        } else if imm <= 0xFFF {
6151            // SUBW (T4): plain 12-bit immediate (0..4095). Makes
6152            // `sub sp, sp, #frame` correct for frame sizes >= 256.
6153            0xF2A0
6154        } else {
6155            return Err(synth_core::Error::synthesis(
6156                "SUB immediate > 0xFFF (4095) requires a multi-instruction sequence (not supported)",
6157            ));
6158        };
6159
6160        let hw1: u16 = (hw1_base | (i_bit << 10) | rn_bits) as u16;
6161        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6162
6163        let mut bytes = hw1.to_le_bytes().to_vec();
6164        bytes.extend_from_slice(&hw2.to_le_bytes());
6165        Ok(bytes)
6166    }
6167
6168    /// Encode Thumb-2 32-bit ADDS with immediate (sets flags)
6169    fn encode_thumb32_adds(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6170        let rd_bits = reg_to_bits(rd);
6171        let rn_bits = reg_to_bits(rn);
6172
6173        // ADDS.W (flag-setting) has only the modified-immediate form — error on
6174        // an un-encodable value rather than silently add the wrong constant.
6175        let field = try_thumb_expand_imm(imm).ok_or_else(|| {
6176            synth_core::Error::synthesis(
6177                "ADDS immediate is not a valid ThumbExpandImm — materialize into a register",
6178            )
6179        })?;
6180        let i_bit = (field >> 11) & 1;
6181        let imm3 = (field >> 8) & 0x7;
6182        let imm8 = field & 0xFF;
6183
6184        // ADDS.W Rd, Rn, #imm (with S=1)
6185        // First halfword: 1111 0 i 0 1000 1 Rn = F110 | i<<10 | Rn
6186        let hw1: u16 = (0xF110 | (i_bit << 10) | rn_bits) as u16;
6187        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6188
6189        let mut bytes = hw1.to_le_bytes().to_vec();
6190        bytes.extend_from_slice(&hw2.to_le_bytes());
6191        Ok(bytes)
6192    }
6193
6194    /// Encode Thumb-2 32-bit SUBS with immediate (sets flags)
6195    fn encode_thumb32_subs(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6196        let rd_bits = reg_to_bits(rd);
6197        let rn_bits = reg_to_bits(rn);
6198
6199        // SUBS.W (flag-setting) has only the modified-immediate form — error on
6200        // an un-encodable value rather than silently subtract the wrong constant.
6201        let field = try_thumb_expand_imm(imm).ok_or_else(|| {
6202            synth_core::Error::synthesis(
6203                "SUBS immediate is not a valid ThumbExpandImm — materialize into a register",
6204            )
6205        })?;
6206        let i_bit = (field >> 11) & 1;
6207        let imm3 = (field >> 8) & 0x7;
6208        let imm8 = field & 0xFF;
6209
6210        // SUBS.W Rd, Rn, #imm (with S=1)
6211        // First halfword: 1111 0 i 0 1101 1 Rn = F1B0 | i<<10 | Rn
6212        let hw1: u16 = (0xF1B0 | (i_bit << 10) | rn_bits) as u16;
6213        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6214
6215        let mut bytes = hw1.to_le_bytes().to_vec();
6216        bytes.extend_from_slice(&hw2.to_le_bytes());
6217        Ok(bytes)
6218    }
6219
6220    /// Encode Thumb-2 32-bit MOVW (16-bit immediate)
6221    ///
6222    /// # Contract (Verus-style)
6223    /// ```text
6224    /// requires rd <= R14
6225    /// ensures result.len() == 4
6226    /// ensures (imm & 0xFFFF) can be reconstructed from the encoding
6227    /// ```
6228    fn encode_thumb32_movw(&self, rd: &Reg, imm: u32) -> Result<Vec<u8>> {
6229        let rd_bits = reg_to_bits(rd);
6230        reg_bits_checked(rd_bits)?;
6231        let imm16 = imm & 0xFFFF;
6232
6233        // MOVW Rd, #imm16
6234        // 1111 0 i 10 0 1 0 0 imm4 | 0 imm3 Rd imm8
6235        let imm4 = (imm16 >> 12) & 0xF;
6236        let i_bit = (imm16 >> 11) & 1;
6237        let imm3 = (imm16 >> 8) & 0x7;
6238        let imm8 = imm16 & 0xFF;
6239
6240        let hw1: u16 = (0xF240 | (i_bit << 10) | imm4) as u16;
6241        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6242
6243        let mut bytes = hw1.to_le_bytes().to_vec();
6244        bytes.extend_from_slice(&hw2.to_le_bytes());
6245        encoding_contracts::verify_thumb32(&bytes);
6246        Ok(bytes)
6247    }
6248
6249    /// Encode Thumb-2 32-bit shift with immediate
6250    ///
6251    /// # Contract (Verus-style)
6252    /// ```text
6253    /// requires rd <= R14, rm <= R14
6254    /// ensures result.len() == 4
6255    /// ```
6256    fn encode_thumb32_shift(
6257        &self,
6258        rd: &Reg,
6259        rm: &Reg,
6260        shift: u32,
6261        shift_type: u8,
6262    ) -> Result<Vec<u8>> {
6263        let rd_bits = reg_to_bits(rd);
6264        let rm_bits = reg_to_bits(rm);
6265        reg_bits_checked(rd_bits)?;
6266        reg_bits_checked(rm_bits)?;
6267        let imm5 = shift & 0x1F;
6268        let imm2 = imm5 & 0x3;
6269        let imm3 = (imm5 >> 2) & 0x7;
6270
6271        // MOV.W Rd, Rm, <shift> #imm
6272        // EA4F 0 imm3 Rd imm2 type Rm
6273        let hw1: u16 = 0xEA4F;
6274        let hw2: u16 =
6275            ((imm3 << 12) | (rd_bits << 8) | (imm2 << 6) | ((shift_type as u32) << 4) | rm_bits)
6276                as u16;
6277
6278        let mut bytes = hw1.to_le_bytes().to_vec();
6279        bytes.extend_from_slice(&hw2.to_le_bytes());
6280        Ok(bytes)
6281    }
6282
6283    /// Encode Thumb-2 32-bit shift by register
6284    /// Encoding: 11111010 0xx0 Rn | 1111 Rd 0000 Rm
6285    /// shift_type: 00=LSL, 01=LSR, 10=ASR, 11=ROR
6286    fn encode_thumb32_shift_reg(
6287        &self,
6288        rd: &Reg,
6289        rn: &Reg,
6290        rm: &Reg,
6291        shift_type: u8,
6292    ) -> Result<Vec<u8>> {
6293        let rd_bits = reg_to_bits(rd);
6294        let rn_bits = reg_to_bits(rn);
6295        let rm_bits = reg_to_bits(rm);
6296
6297        // hw1: 1111 1010 0xx0 Rn
6298        let hw1: u16 = (0xFA00 | ((shift_type as u32) << 5) | rn_bits) as u16;
6299        // hw2: 1111 Rd 0000 Rm
6300        let hw2: u16 = (0xF000 | (rd_bits << 8) | rm_bits) as u16;
6301
6302        let mut bytes = hw1.to_le_bytes().to_vec();
6303        bytes.extend_from_slice(&hw2.to_le_bytes());
6304        Ok(bytes)
6305    }
6306
6307    /// Encode Thumb-2 32-bit CMP with immediate
6308    fn encode_thumb32_cmp_imm(&self, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6309        let rn_bits = reg_to_bits(rn);
6310
6311        // CMP.W has only the modified-immediate form (no plain-imm12 like ADDW),
6312        // so an un-encodable immediate MUST be materialized into a register by
6313        // the selector. Error rather than silently compare the wrong constant.
6314        let field = try_thumb_expand_imm(imm).ok_or_else(|| {
6315            synth_core::Error::synthesis(
6316                "CMP immediate is not a valid ThumbExpandImm — materialize into a register",
6317            )
6318        })?;
6319        let i_bit = (field >> 11) & 1;
6320        let imm3 = (field >> 8) & 0x7;
6321        let imm8 = field & 0xFF;
6322
6323        // CMP.W Rn, #imm
6324        let hw1: u16 = (0xF1B0 | (i_bit << 10) | rn_bits) as u16;
6325        let hw2: u16 = ((imm3 << 12) | 0x0F00 | imm8) as u16;
6326
6327        let mut bytes = hw1.to_le_bytes().to_vec();
6328        bytes.extend_from_slice(&hw2.to_le_bytes());
6329        Ok(bytes)
6330    }
6331
6332    /// Encode Thumb-2 32-bit LDR
6333    fn encode_thumb32_ldr(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6334        let rd_bits = reg_to_bits(rd);
6335        let base_bits = reg_to_bits(base);
6336
6337        // LDR.W Rd, [Rn, #imm12]
6338        check_ldst_imm12(offset)?;
6339        let hw1: u16 = (0xF8D0 | base_bits) as u16;
6340        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6341
6342        let mut bytes = hw1.to_le_bytes().to_vec();
6343        bytes.extend_from_slice(&hw2.to_le_bytes());
6344        Ok(bytes)
6345    }
6346
6347    /// Encode Thumb-2 32-bit STR
6348    fn encode_thumb32_str(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6349        let rd_bits = reg_to_bits(rd);
6350        let base_bits = reg_to_bits(base);
6351
6352        // STR.W Rd, [Rn, #imm12]
6353        check_ldst_imm12(offset)?;
6354        let hw1: u16 = (0xF8C0 | base_bits) as u16;
6355        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6356
6357        let mut bytes = hw1.to_le_bytes().to_vec();
6358        bytes.extend_from_slice(&hw2.to_le_bytes());
6359        Ok(bytes)
6360    }
6361
6362    /// Encode Thumb-2 32-bit LDR with register offset: LDR.W Rd, [Rn, Rm]
6363    fn encode_thumb32_ldr_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6364        let rd_bits = reg_to_bits(rd);
6365        let base_bits = reg_to_bits(base);
6366        let rm_bits = reg_to_bits(offset_reg);
6367
6368        // LDR.W Rd, [Rn, Rm, LSL #0]
6369        // Encoding: 1111 1000 0101 Rn | Rt 0000 00 imm2 Rm
6370        // imm2 = 00 for no shift (LSL #0)
6371        let hw1: u16 = (0xF850 | base_bits) as u16;
6372        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6373
6374        let mut bytes = hw1.to_le_bytes().to_vec();
6375        bytes.extend_from_slice(&hw2.to_le_bytes());
6376        Ok(bytes)
6377    }
6378
6379    /// Encode Thumb-2 32-bit STR with register offset: STR.W Rd, [Rn, Rm]
6380    fn encode_thumb32_str_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6381        let rd_bits = reg_to_bits(rd);
6382        let base_bits = reg_to_bits(base);
6383        let rm_bits = reg_to_bits(offset_reg);
6384
6385        // STR.W Rd, [Rn, Rm, LSL #0]
6386        // Encoding: 1111 1000 0100 Rn | Rt 0000 00 imm2 Rm
6387        // imm2 = 00 for no shift (LSL #0)
6388        let hw1: u16 = (0xF840 | base_bits) as u16;
6389        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6390
6391        let mut bytes = hw1.to_le_bytes().to_vec();
6392        bytes.extend_from_slice(&hw2.to_le_bytes());
6393        Ok(bytes)
6394    }
6395
6396    // === Sub-word load/store Thumb-2 encoding helpers ===
6397
6398    /// Encode Thumb-2 32-bit LDRB with immediate: LDRB.W Rd, [Rn, #imm12]
6399    fn encode_thumb32_ldrb_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6400        let rd_bits = reg_to_bits(rd);
6401        let base_bits = reg_to_bits(base);
6402        // LDRB.W Rd, [Rn, #imm12]: 1111 1000 1001 Rn | Rt imm12
6403        check_ldst_imm12(offset)?;
6404        let hw1: u16 = (0xF890 | base_bits) as u16;
6405        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6406        let mut bytes = hw1.to_le_bytes().to_vec();
6407        bytes.extend_from_slice(&hw2.to_le_bytes());
6408        Ok(bytes)
6409    }
6410
6411    /// Encode Thumb-2 32-bit LDRB with register: LDRB.W Rd, [Rn, Rm]
6412    fn encode_thumb32_ldrb_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6413        let rd_bits = reg_to_bits(rd);
6414        let base_bits = reg_to_bits(base);
6415        let rm_bits = reg_to_bits(offset_reg);
6416        // LDRB.W Rd, [Rn, Rm, LSL #0]: 1111 1000 0001 Rn | Rt 0000 00 imm2 Rm
6417        let hw1: u16 = (0xF810 | base_bits) as u16;
6418        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6419        let mut bytes = hw1.to_le_bytes().to_vec();
6420        bytes.extend_from_slice(&hw2.to_le_bytes());
6421        Ok(bytes)
6422    }
6423
6424    /// Encode Thumb-2 32-bit LDRSB with immediate: LDRSB.W Rd, [Rn, #imm12]
6425    fn encode_thumb32_ldrsb_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6426        let rd_bits = reg_to_bits(rd);
6427        let base_bits = reg_to_bits(base);
6428        // LDRSB.W Rd, [Rn, #imm12]: 1111 1001 1001 Rn | Rt imm12
6429        check_ldst_imm12(offset)?;
6430        let hw1: u16 = (0xF990 | base_bits) as u16;
6431        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6432        let mut bytes = hw1.to_le_bytes().to_vec();
6433        bytes.extend_from_slice(&hw2.to_le_bytes());
6434        Ok(bytes)
6435    }
6436
6437    /// Encode Thumb-2 32-bit LDRSB with register: LDRSB.W Rd, [Rn, Rm]
6438    fn encode_thumb32_ldrsb_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6439        let rd_bits = reg_to_bits(rd);
6440        let base_bits = reg_to_bits(base);
6441        let rm_bits = reg_to_bits(offset_reg);
6442        // LDRSB.W Rd, [Rn, Rm, LSL #0]: 1111 1001 0001 Rn | Rt 0000 00 imm2 Rm
6443        let hw1: u16 = (0xF910 | base_bits) as u16;
6444        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6445        let mut bytes = hw1.to_le_bytes().to_vec();
6446        bytes.extend_from_slice(&hw2.to_le_bytes());
6447        Ok(bytes)
6448    }
6449
6450    /// Encode Thumb-2 32-bit LDRH with immediate: LDRH.W Rd, [Rn, #imm12]
6451    fn encode_thumb32_ldrh_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6452        let rd_bits = reg_to_bits(rd);
6453        let base_bits = reg_to_bits(base);
6454        // LDRH.W Rd, [Rn, #imm12]: 1111 1000 1011 Rn | Rt imm12
6455        check_ldst_imm12(offset)?;
6456        let hw1: u16 = (0xF8B0 | base_bits) as u16;
6457        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6458        let mut bytes = hw1.to_le_bytes().to_vec();
6459        bytes.extend_from_slice(&hw2.to_le_bytes());
6460        Ok(bytes)
6461    }
6462
6463    /// Encode Thumb-2 32-bit LDRH with register: LDRH.W Rd, [Rn, Rm]
6464    fn encode_thumb32_ldrh_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6465        let rd_bits = reg_to_bits(rd);
6466        let base_bits = reg_to_bits(base);
6467        let rm_bits = reg_to_bits(offset_reg);
6468        // LDRH.W Rd, [Rn, Rm, LSL #0]: 1111 1000 0011 Rn | Rt 0000 00 imm2 Rm
6469        let hw1: u16 = (0xF830 | base_bits) as u16;
6470        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6471        let mut bytes = hw1.to_le_bytes().to_vec();
6472        bytes.extend_from_slice(&hw2.to_le_bytes());
6473        Ok(bytes)
6474    }
6475
6476    /// Encode Thumb-2 32-bit LDRSH with immediate: LDRSH.W Rd, [Rn, #imm12]
6477    fn encode_thumb32_ldrsh_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6478        let rd_bits = reg_to_bits(rd);
6479        let base_bits = reg_to_bits(base);
6480        // LDRSH.W Rd, [Rn, #imm12]: 1111 1001 1011 Rn | Rt imm12
6481        check_ldst_imm12(offset)?;
6482        let hw1: u16 = (0xF9B0 | base_bits) as u16;
6483        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6484        let mut bytes = hw1.to_le_bytes().to_vec();
6485        bytes.extend_from_slice(&hw2.to_le_bytes());
6486        Ok(bytes)
6487    }
6488
6489    /// Encode Thumb-2 32-bit LDRSH with register: LDRSH.W Rd, [Rn, Rm]
6490    fn encode_thumb32_ldrsh_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6491        let rd_bits = reg_to_bits(rd);
6492        let base_bits = reg_to_bits(base);
6493        let rm_bits = reg_to_bits(offset_reg);
6494        // LDRSH.W Rd, [Rn, Rm, LSL #0]: 1111 1001 0011 Rn | Rt 0000 00 imm2 Rm
6495        let hw1: u16 = (0xF930 | base_bits) as u16;
6496        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6497        let mut bytes = hw1.to_le_bytes().to_vec();
6498        bytes.extend_from_slice(&hw2.to_le_bytes());
6499        Ok(bytes)
6500    }
6501
6502    /// Encode Thumb-2 32-bit STRB with immediate: STRB.W Rd, [Rn, #imm12]
6503    fn encode_thumb32_strb_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6504        let rd_bits = reg_to_bits(rd);
6505        let base_bits = reg_to_bits(base);
6506        // STRB.W Rd, [Rn, #imm12]: 1111 1000 1000 Rn | Rt imm12
6507        check_ldst_imm12(offset)?;
6508        let hw1: u16 = (0xF880 | base_bits) as u16;
6509        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6510        let mut bytes = hw1.to_le_bytes().to_vec();
6511        bytes.extend_from_slice(&hw2.to_le_bytes());
6512        Ok(bytes)
6513    }
6514
6515    /// Encode Thumb-2 32-bit STRB with register: STRB.W Rd, [Rn, Rm]
6516    fn encode_thumb32_strb_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6517        let rd_bits = reg_to_bits(rd);
6518        let base_bits = reg_to_bits(base);
6519        let rm_bits = reg_to_bits(offset_reg);
6520        // STRB.W Rd, [Rn, Rm, LSL #0]: 1111 1000 0000 Rn | Rt 0000 00 imm2 Rm
6521        let hw1: u16 = (0xF800 | base_bits) as u16;
6522        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6523        let mut bytes = hw1.to_le_bytes().to_vec();
6524        bytes.extend_from_slice(&hw2.to_le_bytes());
6525        Ok(bytes)
6526    }
6527
6528    /// Encode Thumb-2 32-bit STRH with immediate: STRH.W Rd, [Rn, #imm12]
6529    fn encode_thumb32_strh_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6530        let rd_bits = reg_to_bits(rd);
6531        let base_bits = reg_to_bits(base);
6532        // STRH.W Rd, [Rn, #imm12]: 1111 1000 1010 Rn | Rt imm12
6533        check_ldst_imm12(offset)?;
6534        let hw1: u16 = (0xF8A0 | base_bits) as u16;
6535        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6536        let mut bytes = hw1.to_le_bytes().to_vec();
6537        bytes.extend_from_slice(&hw2.to_le_bytes());
6538        Ok(bytes)
6539    }
6540
6541    /// Encode Thumb-2 32-bit STRH with register: STRH.W Rd, [Rn, Rm]
6542    fn encode_thumb32_strh_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6543        let rd_bits = reg_to_bits(rd);
6544        let base_bits = reg_to_bits(base);
6545        let rm_bits = reg_to_bits(offset_reg);
6546        // STRH.W Rd, [Rn, Rm, LSL #0]: 1111 1000 0010 Rn | Rt 0000 00 imm2 Rm
6547        let hw1: u16 = (0xF820 | base_bits) as u16;
6548        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6549        let mut bytes = hw1.to_le_bytes().to_vec();
6550        bytes.extend_from_slice(&hw2.to_le_bytes());
6551        Ok(bytes)
6552    }
6553
6554    /// Encode Thumb-2 32-bit ADD with immediate: ADD.W Rd, Rn, #imm
6555    fn encode_thumb32_add_imm(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6556        let rd_bits = reg_to_bits(rd);
6557        let rn_bits = reg_to_bits(rn);
6558
6559        // For small immediates, use ADD.W Rd, Rn, #imm12
6560        // Encoding: 1111 0 i 0 1 0 0 0 S Rn | 0 imm3 Rd imm8
6561        // S = 0 (don't update flags)
6562        // The 12-bit immediate is encoded as: i:imm3:imm8
6563        // For simplicity, we only support imm <= 0xFFF (direct encoding)
6564        if imm <= 0xFFF {
6565            let i_bit = (imm >> 11) & 1;
6566            let imm3 = (imm >> 8) & 0x7;
6567            let imm8 = imm & 0xFF;
6568
6569            let hw1: u16 = (0xF100 | (i_bit << 10) | rn_bits) as u16;
6570            let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6571
6572            let mut bytes = hw1.to_le_bytes().to_vec();
6573            bytes.extend_from_slice(&hw2.to_le_bytes());
6574            Ok(bytes)
6575        } else {
6576            // For larger immediates, would need MOVW/MOVT + ADD
6577            // For now, return error
6578            Err(synth_core::Error::synthesis(
6579                "ADD immediate too large for single instruction",
6580            ))
6581        }
6582    }
6583
6584    // === Raw encoding helpers for POPCNT (take register numbers directly) ===
6585
6586    /// Encode Thumb-2 32-bit MOVW (16-bit immediate) - raw version
6587    ///
6588    /// # Contract (Verus-style)
6589    /// ```text
6590    /// requires rd <= 14, imm16 <= 0xFFFF
6591    /// ensures result.len() == 4
6592    /// ```
6593    fn encode_thumb32_movw_raw(&self, rd: u32, imm16: u32) -> Result<Vec<u8>> {
6594        reg_bits_checked(rd)?;
6595        encoding_contracts::verify_imm16(imm16);
6596        // MOVW Rd, #imm16
6597        // 1111 0 i 10 0 1 0 0 imm4 | 0 imm3 Rd imm8
6598        let imm16 = imm16 & 0xFFFF;
6599        let imm4 = (imm16 >> 12) & 0xF;
6600        let i_bit = (imm16 >> 11) & 1;
6601        let imm3 = (imm16 >> 8) & 0x7;
6602        let imm8 = imm16 & 0xFF;
6603
6604        let hw1: u16 = (0xF240 | (i_bit << 10) | imm4) as u16;
6605        let hw2: u16 = ((imm3 << 12) | (rd << 8) | imm8) as u16;
6606
6607        let mut bytes = hw1.to_le_bytes().to_vec();
6608        bytes.extend_from_slice(&hw2.to_le_bytes());
6609        encoding_contracts::verify_thumb32(&bytes);
6610        Ok(bytes)
6611    }
6612
6613    /// Encode Thumb-2 32-bit MOVT (move top 16 bits) - raw version
6614    ///
6615    /// # Contract (Verus-style)
6616    /// ```text
6617    /// requires rd <= 14, imm16 <= 0xFFFF
6618    /// ensures result.len() == 4
6619    /// ```
6620    fn encode_thumb32_movt_raw(&self, rd: u32, imm16: u32) -> Result<Vec<u8>> {
6621        reg_bits_checked(rd)?;
6622        encoding_contracts::verify_imm16(imm16);
6623        // MOVT Rd, #imm16
6624        // 1111 0 i 10 1 1 0 0 imm4 | 0 imm3 Rd imm8
6625        let imm16 = imm16 & 0xFFFF;
6626        let imm4 = (imm16 >> 12) & 0xF;
6627        let i_bit = (imm16 >> 11) & 1;
6628        let imm3 = (imm16 >> 8) & 0x7;
6629        let imm8 = imm16 & 0xFF;
6630
6631        let hw1: u16 = (0xF2C0 | (i_bit << 10) | imm4) as u16;
6632        let hw2: u16 = ((imm3 << 12) | (rd << 8) | imm8) as u16;
6633
6634        let mut bytes = hw1.to_le_bytes().to_vec();
6635        bytes.extend_from_slice(&hw2.to_le_bytes());
6636        encoding_contracts::verify_thumb32(&bytes);
6637        Ok(bytes)
6638    }
6639
6640    /// Encode Thumb-2 32-bit LSR (logical shift right) with immediate - raw version
6641    fn encode_thumb32_lsr_raw(&self, rd: u32, rm: u32, shift: u32) -> Result<Vec<u8>> {
6642        // MOV.W Rd, Rm, LSR #imm
6643        // EA4F 0 imm3 Rd imm2 01 Rm
6644        let imm5 = shift & 0x1F;
6645        let imm2 = imm5 & 0x3;
6646        let imm3 = (imm5 >> 2) & 0x7;
6647
6648        let hw1: u16 = 0xEA4F;
6649        let hw2: u16 = ((imm3 << 12) | (rd << 8) | (imm2 << 6) | (0b01 << 4) | rm) as u16;
6650
6651        let mut bytes = hw1.to_le_bytes().to_vec();
6652        bytes.extend_from_slice(&hw2.to_le_bytes());
6653        Ok(bytes)
6654    }
6655
6656    /// Encode Thumb-2 32-bit AND (register) - raw version
6657    fn encode_thumb32_and_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6658        // AND.W Rd, Rn, Rm
6659        // EA00 Rn | 0 Rd 00 00 Rm
6660        let hw1: u16 = (0xEA00 | rn) as u16;
6661        let hw2: u16 = ((rd << 8) | rm) as u16;
6662
6663        let mut bytes = hw1.to_le_bytes().to_vec();
6664        bytes.extend_from_slice(&hw2.to_le_bytes());
6665        Ok(bytes)
6666    }
6667
6668    /// Encode Thumb-2 32-bit AND with immediate - raw version
6669    fn encode_thumb32_and_imm_raw(&self, rd: u32, rn: u32, imm: u32) -> Result<Vec<u8>> {
6670        // AND.W Rd, Rn, #<modified_immediate>
6671        // For small immediates (0-255), the encoding is simpler
6672        // F0 00 Rn | 0 imm3 Rd imm8
6673        let i_bit = (imm >> 11) & 1;
6674        let imm3 = (imm >> 8) & 0x7;
6675        let imm8 = imm & 0xFF;
6676
6677        let hw1: u16 = (0xF000 | (i_bit << 10) | rn) as u16;
6678        let hw2: u16 = ((imm3 << 12) | (rd << 8) | imm8) as u16;
6679
6680        let mut bytes = hw1.to_le_bytes().to_vec();
6681        bytes.extend_from_slice(&hw2.to_le_bytes());
6682        Ok(bytes)
6683    }
6684
6685    /// Encode Thumb-2 32-bit SUB (register) - raw version
6686    fn encode_thumb32_sub_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6687        // SUB.W Rd, Rn, Rm
6688        // EBA0 Rn | 0 Rd 00 00 Rm
6689        let hw1: u16 = (0xEBA0 | rn) as u16;
6690        let hw2: u16 = ((rd << 8) | rm) as u16;
6691
6692        let mut bytes = hw1.to_le_bytes().to_vec();
6693        bytes.extend_from_slice(&hw2.to_le_bytes());
6694        Ok(bytes)
6695    }
6696
6697    /// Encode Thumb-2 32-bit ADD (register) - raw version
6698    fn encode_thumb32_add_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6699        // ADD.W Rd, Rn, Rm
6700        // EB00 Rn | 0 Rd 00 00 Rm
6701        let hw1: u16 = (0xEB00 | rn) as u16;
6702        let hw2: u16 = ((rd << 8) | rm) as u16;
6703
6704        let mut bytes = hw1.to_le_bytes().to_vec();
6705        bytes.extend_from_slice(&hw2.to_le_bytes());
6706        Ok(bytes)
6707    }
6708
6709    /// Encode Thumb-2 32-bit ADDS (register, flag-setting) - raw version.
6710    /// Used as the high-register fallback for `ArmOp::Adds` (i64 low-word add)
6711    /// so R8-R11 pair operands don't overflow the 16-bit field — #178/#180.
6712    fn encode_thumb32_adds_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6713        // ADDS.W Rd, Rn, Rm (T3, S=1): EB10 Rn | 0 Rd 00 00 Rm
6714        let hw1: u16 = (0xEB10 | rn) as u16;
6715        let hw2: u16 = ((rd << 8) | rm) as u16;
6716        let mut bytes = hw1.to_le_bytes().to_vec();
6717        bytes.extend_from_slice(&hw2.to_le_bytes());
6718        Ok(bytes)
6719    }
6720
6721    /// Encode Thumb-2 32-bit SUBS (register, flag-setting) - raw version.
6722    /// High-register fallback for `ArmOp::Subs` (i64 low-word subtract) — #178/#180.
6723    fn encode_thumb32_subs_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6724        // SUBS.W Rd, Rn, Rm (T3, S=1): EBB0 Rn | 0 Rd 00 00 Rm
6725        let hw1: u16 = (0xEBB0 | rn) as u16;
6726        let hw2: u16 = ((rd << 8) | rm) as u16;
6727        let mut bytes = hw1.to_le_bytes().to_vec();
6728        bytes.extend_from_slice(&hw2.to_le_bytes());
6729        Ok(bytes)
6730    }
6731
6732    /// Encode a sequence of ARM instructions
6733    pub fn encode_sequence(&self, ops: &[ArmOp]) -> Result<Vec<u8>> {
6734        let mut code = Vec::new();
6735
6736        for op in ops {
6737            let encoded = self.encode(op)?;
6738            code.extend_from_slice(&encoded);
6739        }
6740
6741        Ok(code)
6742    }
6743}
6744
6745/// Convert register to bit encoding (0-15)
6746/// Reverse of the ARMv7-M `ThumbExpandImm`: given a 32-bit immediate, return the
6747/// 12-bit `i:imm3:imm8` field if it is a representable modified immediate, else
6748/// `None` (the caller must materialize the value into a register). This is the
6749/// shared correct path for the data-processing immediate encoders — without it
6750/// they pack raw bits and silently mis-encode any value `> 0xFF` that isn't a
6751/// modified immediate (the silent-miscompile class behind #251/#253/#255).
6752fn try_thumb_expand_imm(value: u32) -> Option<u32> {
6753    // i:imm3 = 0000 → 8-bit value, zero-extended (00000000 00000000 00000000 XY).
6754    if value <= 0xFF {
6755        return Some(value);
6756    }
6757    let b0 = value & 0xFF; // byte 0
6758    let b1 = (value >> 8) & 0xFF; // byte 1
6759    // 0x00XY00XY (i:imm3 = 0001) — XY in bytes 0 and 2
6760    if value == (b0 << 16) | b0 {
6761        return Some(0x100 | b0);
6762    }
6763    // 0xXY00XY00 (i:imm3 = 0010) — XY in bytes 1 and 3
6764    if value == (b1 << 24) | (b1 << 8) {
6765        return Some(0x200 | b1);
6766    }
6767    // 0xXYXYXYXY (i:imm3 = 0011) — XY in all four bytes
6768    if value == (b0 << 24) | (b0 << 16) | (b0 << 8) | b0 {
6769        return Some(0x300 | b0);
6770    }
6771    // An 8-bit value with bit 7 set, rotated right by 8..=31. `rotate_left(rot)`
6772    // undoes the encoded right rotation; if the result is `1bbbbbbb` (0x80..=0xFF)
6773    // the value is representable. imm12[11:7] = rot, imm12[6:0] = low 7 bits.
6774    for rot in 8..=31u32 {
6775        let unrot = value.rotate_left(rot);
6776        if (0x80..=0xFF).contains(&unrot) {
6777            return Some((rot << 7) | (unrot & 0x7F));
6778        }
6779    }
6780    None
6781}
6782
6783/// Guard a Thumb-2 `LDR/STR Rd, [Rn, #imm12]` offset. The imm12 form supports
6784/// `0..=4095`; a larger offset must be materialized into a register by the
6785/// selector (register-offset addressing). Returning `Err` rather than silently
6786/// masking `offset & 0xFFF` closes the wrong-address miscompile class (#259,
6787/// the load/store sibling of #253/#255).
6788fn check_ldst_imm12(offset: u32) -> Result<()> {
6789    if offset > 0xFFF {
6790        Err(synth_core::Error::synthesis(
6791            "load/store immediate offset > 0xFFF (4095) — materialize the offset into a register",
6792        ))
6793    } else {
6794        Ok(())
6795    }
6796}
6797
6798fn reg_to_bits(reg: &Reg) -> u32 {
6799    match reg {
6800        Reg::R0 => 0,
6801        Reg::R1 => 1,
6802        Reg::R2 => 2,
6803        Reg::R3 => 3,
6804        Reg::R4 => 4,
6805        Reg::R5 => 5,
6806        Reg::R6 => 6,
6807        Reg::R7 => 7,
6808        Reg::R8 => 8,
6809        Reg::R9 => 9,
6810        Reg::R10 => 10,
6811        Reg::R11 => 11,
6812        Reg::R12 => 12,
6813        Reg::SP => 13,
6814        Reg::LR => 14,
6815        Reg::PC => 15,
6816    }
6817}
6818
6819/// Fallible form of the `verify_reg_bits` contract. PC (R15) is not a valid
6820/// data operand for the Thumb-2 encodings that use this guard (SDIV/UDIV/MLS/…
6821/// are UNPREDICTABLE with PC). Synth's own codegen never emits PC there, but
6822/// the encoder must stay *total* over arbitrary `ArmOp` inputs — the fuzz
6823/// harness (`encoder_no_panic`) requires Ok-or-Err, never a panic. Pre-fix, the
6824/// `debug_assert` in `verify_reg_bits` aborted under `-Cdebug-assertions`.
6825/// Returns a typed Err instead. See #185.
6826fn reg_bits_checked(bits: u32) -> Result<()> {
6827    if bits > 14 {
6828        return Err(synth_core::Error::synthesis(format!(
6829            "register bits {bits} (PC/R15) is not a valid operand for this Thumb-2 encoding"
6830        )));
6831    }
6832    Ok(())
6833}
6834
6835/// Try to encode a 32-bit value as an ARM rotated immediate (imm8 ROR 2*rot4).
6836/// Returns Some((encoded_bits, 1)) if representable, None otherwise.
6837fn try_encode_rotated_imm(val: u32) -> Option<(u32, u32)> {
6838    if val == 0 {
6839        return Some((0, 1));
6840    }
6841    for rot in 0..16u32 {
6842        let shift = rot * 2;
6843        // Rotate left by shift (undo the ROR) to see if result fits in 8 bits
6844        let unrotated = val.rotate_left(shift);
6845        if unrotated <= 0xFF {
6846            // Encoded as: rot4(4 bits) | imm8(8 bits) = rotate_imm << 8 | imm8
6847            return Some(((rot << 8) | unrotated, 1));
6848        }
6849    }
6850    None
6851}
6852
6853/// Encode operand2 field and return (bits, immediate_flag).
6854/// For ARM32 mode, immediates use the rotated-immediate encoding (imm8 ROR 2*rot4).
6855/// Panics if an immediate value cannot be represented. Callers that need large
6856/// immediates should use MOVW/MOVT instead of Operand2::Imm.
6857fn encode_operand2(op2: &Operand2) -> (u32, u32) {
6858    match op2 {
6859        Operand2::Imm(val) => {
6860            let uval = *val as u32;
6861            // Attempt rotated-immediate encoding (ARM32 Operand2)
6862            if let Some(encoded) = try_encode_rotated_imm(uval) {
6863                encoded
6864            } else {
6865                // Fallback: mask to 8 bits (legacy behavior for values that
6866                // cannot be represented). This should not be reached for
6867                // correctly-selected instructions; the instruction selector
6868                // must use MOVW/MOVT for large constants.
6869                let imm = uval & 0xFF;
6870                (imm, 1)
6871            }
6872        }
6873
6874        Operand2::Reg(reg) => {
6875            let reg_bits = reg_to_bits(reg);
6876            (reg_bits, 0) // I=0 for register
6877        }
6878
6879        Operand2::RegShift {
6880            rm,
6881            shift: _,
6882            amount,
6883        } => {
6884            // Simplified encoding with shift
6885            let rm_bits = reg_to_bits(rm);
6886            let shift_bits = (*amount & 0x1F) << 7;
6887            (shift_bits | rm_bits, 0)
6888        }
6889    }
6890}
6891
6892/// Encode memory address to (base_reg, offset)
6893fn encode_mem_addr(addr: &MemAddr) -> (u32, u32) {
6894    let base_bits = reg_to_bits(&addr.base);
6895    let offset_bits = (addr.offset as u32) & 0xFFF; // 12-bit offset
6896    (base_bits, offset_bits)
6897}
6898
6899/// S-register number: S0=0, S1=1, ..., S31=31
6900fn vfp_sreg_to_num(reg: &VfpReg) -> Result<u32> {
6901    match reg {
6902        VfpReg::S0 => Ok(0),
6903        VfpReg::S1 => Ok(1),
6904        VfpReg::S2 => Ok(2),
6905        VfpReg::S3 => Ok(3),
6906        VfpReg::S4 => Ok(4),
6907        VfpReg::S5 => Ok(5),
6908        VfpReg::S6 => Ok(6),
6909        VfpReg::S7 => Ok(7),
6910        VfpReg::S8 => Ok(8),
6911        VfpReg::S9 => Ok(9),
6912        VfpReg::S10 => Ok(10),
6913        VfpReg::S11 => Ok(11),
6914        VfpReg::S12 => Ok(12),
6915        VfpReg::S13 => Ok(13),
6916        VfpReg::S14 => Ok(14),
6917        VfpReg::S15 => Ok(15),
6918        VfpReg::S16 => Ok(16),
6919        VfpReg::S17 => Ok(17),
6920        VfpReg::S18 => Ok(18),
6921        VfpReg::S19 => Ok(19),
6922        VfpReg::S20 => Ok(20),
6923        VfpReg::S21 => Ok(21),
6924        VfpReg::S22 => Ok(22),
6925        VfpReg::S23 => Ok(23),
6926        VfpReg::S24 => Ok(24),
6927        VfpReg::S25 => Ok(25),
6928        VfpReg::S26 => Ok(26),
6929        VfpReg::S27 => Ok(27),
6930        VfpReg::S28 => Ok(28),
6931        VfpReg::S29 => Ok(29),
6932        VfpReg::S30 => Ok(30),
6933        VfpReg::S31 => Ok(31),
6934        // D-registers are not used in F32 single-precision encodings
6935        _ => Err(synth_core::Error::SynthesisError(
6936            "D-register not supported in single-precision VFP encoding".to_string(),
6937        )),
6938    }
6939}
6940
6941/// D-register number: D0=0, D1=1, ..., D15=15
6942fn vfp_dreg_to_num(reg: &VfpReg) -> Result<u32> {
6943    match reg {
6944        VfpReg::D0 => Ok(0),
6945        VfpReg::D1 => Ok(1),
6946        VfpReg::D2 => Ok(2),
6947        VfpReg::D3 => Ok(3),
6948        VfpReg::D4 => Ok(4),
6949        VfpReg::D5 => Ok(5),
6950        VfpReg::D6 => Ok(6),
6951        VfpReg::D7 => Ok(7),
6952        VfpReg::D8 => Ok(8),
6953        VfpReg::D9 => Ok(9),
6954        VfpReg::D10 => Ok(10),
6955        VfpReg::D11 => Ok(11),
6956        VfpReg::D12 => Ok(12),
6957        VfpReg::D13 => Ok(13),
6958        VfpReg::D14 => Ok(14),
6959        VfpReg::D15 => Ok(15),
6960        // S-registers are not used in F64 double-precision encodings
6961        _ => Err(synth_core::Error::SynthesisError(
6962            "S-register not supported in double-precision VFP encoding".to_string(),
6963        )),
6964    }
6965}
6966
6967/// Split S-register into (Vx[3:0], qualifier_bit) for VFP encoding.
6968/// For an S-register number s: Vx = s >> 1, qualifier = s & 1.
6969/// The qualifier bit goes to D (bit 22), N (bit 7), or M (bit 5) depending on role.
6970fn encode_sreg(s: u32) -> (u32, u32) {
6971    (s >> 1, s & 1)
6972}
6973
6974/// Split D-register into (Vx[3:0], qualifier_bit) for VFP double-precision encoding.
6975/// For a D-register number d: Vx = d & 0xF, qualifier = (d >> 4) & 1.
6976/// For D0-D15, qualifier is always 0.
6977fn encode_dreg(d: u32) -> (u32, u32) {
6978    (d & 0xF, (d >> 4) & 1)
6979}
6980
6981/// Encode a VFP 3-register arithmetic instruction (VADD.F32, VSUB.F32, VMUL.F32, VDIV.F32).
6982/// Returns the full 32-bit instruction word.
6983///
6984/// VFP encoding: [cond 1110] [D opc1 Vn] [Vd 101 sz] [N opc2 M 0 Vm]
6985/// For single-precision (sz=0), coprocessor = 0xA (bits[11:8]).
6986fn encode_vfp_3reg(base: u32, sd: &VfpReg, sn: &VfpReg, sm: &VfpReg) -> Result<u32> {
6987    let sd_num = vfp_sreg_to_num(sd)?;
6988    let sn_num = vfp_sreg_to_num(sn)?;
6989    let sm_num = vfp_sreg_to_num(sm)?;
6990    let (vd, d) = encode_sreg(sd_num);
6991    let (vn, n) = encode_sreg(sn_num);
6992    let (vm, m) = encode_sreg(sm_num);
6993
6994    Ok(base | (d << 22) | (vn << 16) | (vd << 12) | (n << 7) | (m << 5) | vm)
6995}
6996
6997/// Encode a VFP 2-register instruction (VNEG.F32, VABS.F32, VSQRT.F32).
6998/// Returns the full 32-bit instruction word.
6999fn encode_vfp_2reg(base: u32, sd: &VfpReg, sm: &VfpReg) -> Result<u32> {
7000    let sd_num = vfp_sreg_to_num(sd)?;
7001    let sm_num = vfp_sreg_to_num(sm)?;
7002    let (vd, d) = encode_sreg(sd_num);
7003    let (vm, m) = encode_sreg(sm_num);
7004
7005    Ok(base | (d << 22) | (vd << 12) | (m << 5) | vm)
7006}
7007
7008/// Encode a VFP load/store (VLDR.F32 / VSTR.F32).
7009/// offset is in bytes and must be word-aligned; encoded as imm8 = offset/4.
7010/// U bit (bit 23) controls add/subtract offset.
7011fn encode_vfp_ldst(base: u32, sd: &VfpReg, addr: &MemAddr) -> Result<u32> {
7012    let sd_num = vfp_sreg_to_num(sd)?;
7013    let (vd, d) = encode_sreg(sd_num);
7014    let rn = reg_to_bits(&addr.base);
7015
7016    let offset = addr.offset;
7017    let u_bit = if offset >= 0 { 1u32 } else { 0u32 };
7018    let abs_offset = offset.unsigned_abs();
7019    let imm8 = (abs_offset / 4) & 0xFF;
7020
7021    Ok(base | (u_bit << 23) | (d << 22) | (rn << 16) | (vd << 12) | imm8)
7022}
7023
7024/// Encode VMOV between core register and S-register.
7025/// VMOV Sn, Rt: 0xEE00_0A10 | (Vn << 16) | (N << 7) | (Rt << 12)
7026/// VMOV Rt, Sn: 0xEE10_0A10 | (Vn << 16) | (N << 7) | (Rt << 12)
7027fn encode_vmov_core_sreg(to_sreg: bool, sreg: &VfpReg, core: &Reg) -> Result<u32> {
7028    let s_num = vfp_sreg_to_num(sreg)?;
7029    let (vn, n) = encode_sreg(s_num);
7030    let rt = reg_to_bits(core);
7031
7032    let base = if to_sreg { 0xEE000A10 } else { 0xEE100A10 };
7033    Ok(base | (vn << 16) | (rt << 12) | (n << 7))
7034}
7035
7036/// Encode a VFP 3-register double-precision instruction (VADD.F64, VSUB.F64, etc.).
7037/// For double-precision (sz=1), coprocessor = 0xB (bits[11:8]).
7038/// The base should have bit 8 = 1 for F64 (0xB suffix instead of 0xA).
7039fn encode_vfp_3reg_f64(base: u32, dd: &VfpReg, dn: &VfpReg, dm: &VfpReg) -> Result<u32> {
7040    let dd_num = vfp_dreg_to_num(dd)?;
7041    let dn_num = vfp_dreg_to_num(dn)?;
7042    let dm_num = vfp_dreg_to_num(dm)?;
7043    let (vd, d) = encode_dreg(dd_num);
7044    let (vn, n) = encode_dreg(dn_num);
7045    let (vm, m) = encode_dreg(dm_num);
7046
7047    Ok(base | (d << 22) | (vn << 16) | (vd << 12) | (n << 7) | (m << 5) | vm)
7048}
7049
7050/// Encode a VFP 2-register double-precision instruction (VNEG.F64, VABS.F64, VSQRT.F64).
7051fn encode_vfp_2reg_f64(base: u32, dd: &VfpReg, dm: &VfpReg) -> Result<u32> {
7052    let dd_num = vfp_dreg_to_num(dd)?;
7053    let dm_num = vfp_dreg_to_num(dm)?;
7054    let (vd, d) = encode_dreg(dd_num);
7055    let (vm, m) = encode_dreg(dm_num);
7056
7057    Ok(base | (d << 22) | (vd << 12) | (m << 5) | vm)
7058}
7059
7060/// Encode a VFP load/store for double-precision (VLDR.64 / VSTR.64).
7061/// offset is in bytes and must be word-aligned; encoded as imm8 = offset/4.
7062fn encode_vfp_ldst_f64(base: u32, dd: &VfpReg, addr: &MemAddr) -> Result<u32> {
7063    let dd_num = vfp_dreg_to_num(dd)?;
7064    let (vd, d) = encode_dreg(dd_num);
7065    let rn = reg_to_bits(&addr.base);
7066
7067    let offset = addr.offset;
7068    let u_bit = if offset >= 0 { 1u32 } else { 0u32 };
7069    let abs_offset = offset.unsigned_abs();
7070    let imm8 = (abs_offset / 4) & 0xFF;
7071
7072    Ok(base | (u_bit << 23) | (d << 22) | (rn << 16) | (vd << 12) | imm8)
7073}
7074
7075/// Encode VMOV between two core registers and a D-register.
7076/// VMOV Dm, Rt, Rt2: 0xEC40_0B10 | (Rt2 << 16) | (Rt << 12) | (M << 5) | Vm
7077/// VMOV Rt, Rt2, Dm: 0xEC50_0B10 | (Rt2 << 16) | (Rt << 12) | (M << 5) | Vm
7078fn encode_vmov_core_dreg(
7079    to_dreg: bool,
7080    dreg: &VfpReg,
7081    core_lo: &Reg,
7082    core_hi: &Reg,
7083) -> Result<u32> {
7084    let d_num = vfp_dreg_to_num(dreg)?;
7085    let (vm, m) = encode_dreg(d_num);
7086    let rt = reg_to_bits(core_lo);
7087    let rt2 = reg_to_bits(core_hi);
7088
7089    let base = if to_dreg { 0xEC400B10 } else { 0xEC500B10 };
7090    Ok(base | (rt2 << 16) | (rt << 12) | (m << 5) | vm)
7091}
7092
7093/// Emit a VFP 32-bit instruction as Thumb-2 bytes (two LE halfwords).
7094fn vfp_to_thumb_bytes(instr: u32) -> Vec<u8> {
7095    let hw1 = ((instr >> 16) & 0xFFFF) as u16;
7096    let hw2 = (instr & 0xFFFF) as u16;
7097    let mut bytes = hw1.to_le_bytes().to_vec();
7098    bytes.extend_from_slice(&hw2.to_le_bytes());
7099    bytes
7100}
7101
7102// ============================================================================
7103// Helium MVE encoding helpers
7104// ============================================================================
7105
7106/// Q-register number: Q0=0, Q1=1, ..., Q7=7
7107fn qreg_to_num(reg: &QReg) -> u32 {
7108    match reg {
7109        QReg::Q0 => 0,
7110        QReg::Q1 => 1,
7111        QReg::Q2 => 2,
7112        QReg::Q3 => 3,
7113        QReg::Q4 => 4,
7114        QReg::Q5 => 5,
7115        QReg::Q6 => 6,
7116        QReg::Q7 => 7,
7117    }
7118}
7119
7120/// MVE element size to encoding bits: S8=0b00, S16=0b01, S32=0b10
7121fn mve_size_bits(size: &MveSize) -> u32 {
7122    match size {
7123        MveSize::S8 => 0b00,
7124        MveSize::S16 => 0b01,
7125        MveSize::S32 => 0b10,
7126    }
7127}
7128
7129/// Encode MVE 3-register instruction.
7130/// Q-registers are encoded as D-register pairs: Q0=D0:D1, Q1=D2:D3, etc.
7131/// In NEON/MVE encoding, the Q-register uses D-register number = Qn * 2.
7132fn encode_mve_3reg(base: u32, qd: &QReg, qn: &QReg, qm: &QReg) -> u32 {
7133    let d = qreg_to_num(qd) * 2;
7134    let n = qreg_to_num(qn) * 2;
7135    let m = qreg_to_num(qm) * 2;
7136
7137    // Standard NEON/MVE 3-register encoding:
7138    // D bit (bit 22) = Vd[4], Vd[3:0] = bits [15:12]
7139    // N bit (bit 7)  = Vn[4], Vn[3:0] = bits [19:16]
7140    // M bit (bit 5)  = Vm[4], Vm[3:0] = bits [3:0]
7141    let vd = d & 0xF;
7142    let d_bit = (d >> 4) & 1;
7143    let vn = n & 0xF;
7144    let n_bit = (n >> 4) & 1;
7145    let vm = m & 0xF;
7146    let m_bit = (m >> 4) & 1;
7147
7148    base | (d_bit << 22) | (vn << 16) | (vd << 12) | (n_bit << 7) | (m_bit << 5) | vm
7149}
7150
7151/// Encode MVE 3-register bitwise instruction (VAND, VORR, VEOR, VBIC).
7152fn encode_mve_3reg_bitwise(base: u32, qd: &QReg, qn: &QReg, qm: &QReg) -> u32 {
7153    encode_mve_3reg(base, qd, qn, qm)
7154}
7155
7156/// Encode MVE VLDRW.32 Qd, [Rn, #offset]
7157/// Format: EC9x xxxx - contiguous load, word-sized elements
7158fn encode_mve_vldrw(qd: &QReg, addr: &MemAddr) -> u32 {
7159    let qd_enc = qreg_to_num(qd) * 2;
7160    let rn = reg_to_bits(&addr.base);
7161    let offset = addr.offset;
7162    let u_bit = if offset >= 0 { 1u32 } else { 0u32 };
7163    let abs_offset = offset.unsigned_abs();
7164    let imm7 = (abs_offset / 4) & 0x7F; // 7-bit word-aligned offset
7165
7166    // VLDRW.32 Qd, [Rn, #imm]: ED10 xx80 variant
7167    0xED100E80
7168        | (u_bit << 23)
7169        | ((qd_enc >> 4) << 22)
7170        | (rn << 16)
7171        | ((qd_enc & 0xF) << 12)
7172        | (imm7 & 0x7F)
7173}
7174
7175/// Encode MVE VSTRW.32 Qd, [Rn, #offset]
7176fn encode_mve_vstrw(qd: &QReg, addr: &MemAddr) -> u32 {
7177    let qd_enc = qreg_to_num(qd) * 2;
7178    let rn = reg_to_bits(&addr.base);
7179    let offset = addr.offset;
7180    let u_bit = if offset >= 0 { 1u32 } else { 0u32 };
7181    let abs_offset = offset.unsigned_abs();
7182    let imm7 = (abs_offset / 4) & 0x7F;
7183
7184    0xED000E80
7185        | (u_bit << 23)
7186        | ((qd_enc >> 4) << 22)
7187        | (rn << 16)
7188        | ((qd_enc & 0xF) << 12)
7189        | (imm7 & 0x7F)
7190}
7191
7192impl ArmEncoder {
7193    /// Encode MVE constant load: MOVW+MOVT+VMOV for each 32-bit word, then assemble Q-register
7194    fn encode_thumb_mve_const(&self, qd: &QReg, bytes: &[u8; 16]) -> Result<Vec<u8>> {
7195        let mut result = Vec::new();
7196        let qd_num = qreg_to_num(qd);
7197
7198        // Load each 32-bit word into R12 (temp) then VMOV into S-register
7199        for i in 0..4 {
7200            let word = u32::from_le_bytes([
7201                bytes[i * 4],
7202                bytes[i * 4 + 1],
7203                bytes[i * 4 + 2],
7204                bytes[i * 4 + 3],
7205            ]);
7206            let lo16 = word & 0xFFFF;
7207            let hi16 = (word >> 16) & 0xFFFF;
7208
7209            // MOVW R12, #lo16
7210            result.extend_from_slice(&self.encode_thumb32_movw_raw(12, lo16)?);
7211            // MOVT R12, #hi16
7212            if hi16 != 0 {
7213                result.extend_from_slice(&self.encode_thumb32_movt_raw(12, hi16)?);
7214            }
7215
7216            // VMOV Sn, R12 where Sn = Qd*4 + i
7217            let s_num = qd_num * 4 + i as u32;
7218            let (vn, n) = encode_sreg(s_num);
7219            let vmov: u32 = 0xEE000A10 | (vn << 16) | (12 << 12) | (n << 7);
7220            result.extend_from_slice(&vfp_to_thumb_bytes(vmov));
7221        }
7222
7223        Ok(result)
7224    }
7225
7226    /// Encode lane-wise f32 binary operation (VDIV, etc.) via S-register extraction
7227    fn encode_thumb_mve_lane_wise_f32_binop(
7228        &self,
7229        qd: &QReg,
7230        qn: &QReg,
7231        qm: &QReg,
7232        vfp_base: u32,
7233    ) -> Result<Vec<u8>> {
7234        let mut result = Vec::new();
7235        let qd_num = qreg_to_num(qd);
7236        let qn_num = qreg_to_num(qn);
7237        let qm_num = qreg_to_num(qm);
7238
7239        // For each lane 0..3: use S-registers directly (Q aliasing)
7240        for i in 0..4u32 {
7241            let sd = qd_num * 4 + i;
7242            let sn = qn_num * 4 + i;
7243            let sm = qm_num * 4 + i;
7244
7245            let (vd, d) = encode_sreg(sd);
7246            let (vn, n) = encode_sreg(sn);
7247            let (vm, m) = encode_sreg(sm);
7248
7249            let instr = vfp_base | (d << 22) | (vn << 16) | (vd << 12) | (n << 7) | (m << 5) | vm;
7250            result.extend_from_slice(&vfp_to_thumb_bytes(instr));
7251        }
7252
7253        Ok(result)
7254    }
7255
7256    /// Encode lane-wise f32 VSQRT via S-register extraction
7257    fn encode_thumb_mve_lane_wise_f32_sqrt(&self, qd: &QReg, qm: &QReg) -> Result<Vec<u8>> {
7258        let mut result = Vec::new();
7259        let qd_num = qreg_to_num(qd);
7260        let qm_num = qreg_to_num(qm);
7261
7262        // VSQRT.F32 base: 0xEEB10AC0
7263        for i in 0..4u32 {
7264            let sd = qd_num * 4 + i;
7265            let sm = qm_num * 4 + i;
7266
7267            let (vd, d) = encode_sreg(sd);
7268            let (vm, m) = encode_sreg(sm);
7269
7270            let instr: u32 = 0xEEB10AC0 | (d << 22) | (vd << 12) | (m << 5) | vm;
7271            result.extend_from_slice(&vfp_to_thumb_bytes(instr));
7272        }
7273
7274        Ok(result)
7275    }
7276}
7277
7278#[cfg(test)]
7279mod tests {
7280    use super::*;
7281
7282    #[test]
7283    fn test_encoder_creation() {
7284        let encoder_arm = ArmEncoder::new_arm32();
7285        assert!(!encoder_arm.thumb_mode);
7286
7287        let encoder_thumb = ArmEncoder::new_thumb2();
7288        assert!(encoder_thumb.thumb_mode);
7289    }
7290
7291    /// #204 WAKE-path regression: `SetCond` materialized 0/1 with the 16-bit
7292    /// `MOVS Rd,#imm` (T1), whose Rd field is 3 bits (R0–R7). For a high Rd
7293    /// (R8–R12) `rd_bits << 8` overflows bit 11, flipping the opcode MOVS→CMP
7294    /// (`0x2c00`), so the boolean was never written — gale's `has_waiter` kept a
7295    /// stale value and the binary-sem WAKE dispatch read garbage. High Rd must
7296    /// use the 32-bit `MOV.W` (T2). Verify the bytes, not the IR.
7297    /// #311: the SAME high-Rd MOVS→CMP transmutation as #204, but in the
7298    /// i64 comparison expansions (I64SetCond / I64SetCondZ) — missed by the
7299    /// #204 hardening. With rd=R8 the boolean died in the flags
7300    /// (`ite eq; cmpeq r0,#1; cmpne r0,#0`), so gale's packed-u64 select
7301    /// read a stale register on silicon. High Rd must take MOV.W / CMP.W.
7302    #[test]
7303    fn test_encode_i64setcond_high_reg_uses_mov_w_311() {
7304        use synth_synthesis::{ArmOp, Condition, Reg};
7305        let enc = ArmEncoder::new_thumb2();
7306        let bytes = enc
7307            .encode(&ArmOp::I64SetCond {
7308                rd: Reg::R8,
7309                rn_lo: Reg::R2,
7310                rn_hi: Reg::R3,
7311                rm_lo: Reg::R6,
7312                rm_hi: Reg::R7,
7313                cond: Condition::EQ,
7314            })
7315            .unwrap();
7316        // The 32-bit MOV.W immediate (T2) first halfword is 0xF04F; the
7317        // 16-bit transmuted forms would contain 0x2801/0x2800 (CMP r0,#1/#0).
7318        let halfwords: Vec<u16> = bytes
7319            .chunks(2)
7320            .map(|c| u16::from_le_bytes([c[0], c[1]]))
7321            .collect();
7322        assert!(
7323            halfwords.iter().filter(|&&h| h == 0xF04F).count() == 2,
7324            "high rd must use two MOV.W (T2) encodings, got {halfwords:04x?}"
7325        );
7326        assert!(
7327            !halfwords.contains(&0x2801) && !halfwords.contains(&0x2800),
7328            "no transmuted 16-bit CMP imm: {halfwords:04x?}"
7329        );
7330
7331        let bytes_z = enc
7332            .encode(&ArmOp::I64SetCondZ {
7333                rd: Reg::R8,
7334                rn_lo: Reg::R2,
7335                rn_hi: Reg::R3,
7336            })
7337            .unwrap();
7338        let hw_z: Vec<u16> = bytes_z
7339            .chunks(2)
7340            .map(|c| u16::from_le_bytes([c[0], c[1]]))
7341            .collect();
7342        assert!(
7343            hw_z.iter().filter(|&&h| h == 0xF04F).count() == 2,
7344            "SetCondZ high rd MOV.W: {hw_z:04x?}"
7345        );
7346        // CMP.W rd,#0 (T2) first halfword: 0xF1B0 | rd
7347        assert!(
7348            hw_z.contains(&(0xF1B0 | 8)),
7349            "SetCondZ high rd must use CMP.W: {hw_z:04x?}"
7350        );
7351    }
7352
7353    #[test]
7354    fn test_encode_setcond_high_reg_uses_mov_w_204() {
7355        use synth_synthesis::{ArmOp, Condition, Reg};
7356        let enc = ArmEncoder::new_thumb2();
7357        // R12 (high): must be ITE + MOV.W #1 + MOV.W #0, never a 16-bit MOVS/CMP.
7358        let hi = enc
7359            .encode(&ArmOp::SetCond {
7360                rd: Reg::R12,
7361                cond: Condition::NE,
7362            })
7363            .unwrap();
7364        assert_eq!(hi.len(), 10, "ITE(2) + MOV.W(4) + MOV.W(4): {hi:02x?}");
7365        // both value halfwords are MOV.W (0xF04F) — NOT the corrupt CMP (0x2c..).
7366        assert_eq!(&hi[2..4], &[0x4F, 0xF0], "then = MOV.W: {hi:02x?}");
7367        assert_eq!(&hi[6..8], &[0x4F, 0xF0], "else = MOV.W: {hi:02x?}");
7368        assert_eq!(hi[4] & 0x0F, 0x01, "then imm = #1");
7369        assert_eq!(hi[8] & 0x0F, 0x00, "else imm = #0");
7370        // Low Rd keeps the compact 16-bit MOVS form.
7371        let lo = enc
7372            .encode(&ArmOp::SetCond {
7373                rd: Reg::R0,
7374                cond: Condition::NE,
7375            })
7376            .unwrap();
7377        assert_eq!(lo.len(), 6, "ITE(2) + MOVS(2) + MOVS(2): {lo:02x?}");
7378        assert_eq!(lo[2..4], [0x01, 0x20], "then = MOVS R0,#1");
7379        assert_eq!(lo[4..6], [0x00, 0x20], "else = MOVS R0,#0");
7380    }
7381
7382    /// #209 Opt 1b: UMULL RdLo, RdHi, Rn, Rm encodes correctly on both ISAs.
7383    /// Thumb-2 T1: 1111 1011 1010 Rn | RdLo RdHi 0000 Rm.
7384    /// A32:        cond 0000 1000 RdHi RdLo Rm 1001 Rn.
7385    #[test]
7386    fn test_encode_umull_209b() {
7387        use synth_synthesis::{ArmOp, Reg};
7388        let op = ArmOp::Umull {
7389            rdlo: Reg::R4,
7390            rdhi: Reg::R5,
7391            rn: Reg::R0,
7392            rm: Reg::R3,
7393        };
7394        // Thumb-2: hw1 = 0xFBA0 | 0 = 0xFBA0; hw2 = (4<<12)|(5<<8)|3 = 0x4503.
7395        let t = ArmEncoder::new_thumb2().encode(&op).unwrap();
7396        assert_eq!(
7397            t,
7398            vec![0xA0, 0xFB, 0x03, 0x45],
7399            "umull r4,r5,r0,r3 (T2): {t:02x?}"
7400        );
7401        // A32: 0xE0800090 | (5<<16) | (4<<12) | (3<<8) | 0 = 0xE0854390.
7402        let a = ArmEncoder::new_arm32().encode(&op).unwrap();
7403        assert_eq!(
7404            a,
7405            0xE085_4390u32.to_le_bytes().to_vec(),
7406            "umull (A32): {a:02x?}"
7407        );
7408    }
7409
7410    /// #206 regression: the ARM32 (A32) `Ldr`/`Str` encoders fed `addr` through
7411    /// `encode_mem_addr`, which returns only the 12-bit immediate — so a register
7412    /// offset (`[rn, rm, #off]`) was silently dropped to `[rn, #off]`, sending
7413    /// the access to the wrong runtime address (silent miscompile on the default
7414    /// `--target arm`). A register offset must materialize `ip = rn + rm` and
7415    /// load from `[ip, #off]`. Verify the bytes.
7416    #[test]
7417    fn test_encode_arm32_indexed_load_keeps_index_206() {
7418        use synth_synthesis::{ArmOp, MemAddr, Reg};
7419        let enc = ArmEncoder::new_arm32();
7420        // ldr r0, [r11, r1, #8]  must NOT collapse to a single immediate ldr.
7421        let bytes = enc
7422            .encode(&ArmOp::Ldr {
7423                rd: Reg::R0,
7424                addr: MemAddr::reg_imm(Reg::R11, Reg::R1, 8),
7425            })
7426            .unwrap();
7427        assert_eq!(
7428            bytes.len(),
7429            8,
7430            "expected ADD ip + LDR (2 words): {bytes:02x?}"
7431        );
7432        let add = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
7433        let ldr = u32::from_le_bytes(bytes[4..8].try_into().unwrap());
7434        // ADD ip, r11, r1  = 0xE08BC001
7435        assert_eq!(add, 0xE08B_C001, "ADD ip,r11,r1: {add:#010x}");
7436        // LDR r0, [ip, #8] = 0xE59C0008
7437        assert_eq!(ldr, 0xE59C_0008, "LDR r0,[ip,#8]: {ldr:#010x}");
7438        // A bare immediate ldr (the bug) would be 0xE59B0008 (base=r11) — reject.
7439        assert_ne!(ldr, 0xE59B_0008, "index must not be dropped");
7440    }
7441
7442    /// #178/#180 regression: the Thumb `Add`/`Adds`/`Subs` reg-forms used the
7443    /// 16-bit encoding unconditionally. For high registers (R12 base scratch,
7444    /// R8-R11 i64 pairs) the 3-bit register fields overflow and corrupt the
7445    /// operands — `add ip,ip,r0` came out as `adds r4,r5,r1` (0x186C), silently
7446    /// dropping the address operand and miscompiling every optimized memory
7447    /// access. High registers must use the 32-bit `.W` forms.
7448    #[test]
7449    fn test_encode_thumb_add_high_reg_uses_add_w_178_180() {
7450        let encoder = ArmEncoder::new_thumb2();
7451
7452        // add ip, ip, r0  — the exact MemLoad/MemStore base+addr op.
7453        let code = encoder
7454            .encode(&ArmOp::Add {
7455                rd: Reg::R12,
7456                rn: Reg::R12,
7457                op2: Operand2::Reg(Reg::R0),
7458            })
7459            .unwrap();
7460        // ADD.W ip, ip, r0 = EB0C 0C00 (little-endian halfwords).
7461        assert_eq!(
7462            code,
7463            vec![0x0C, 0xEB, 0x00, 0x0C],
7464            "high-reg Thumb ADD must be 32-bit ADD.W (EB0C 0C00), not corrupt 16-bit; got {code:02X?}"
7465        );
7466        // Must NOT be the buggy 16-bit 0x186C (`adds r4,r5,r1`).
7467        assert_ne!(code, vec![0x6C, 0x18], "regressed to corrupt 16-bit ADDS");
7468
7469        // Low-register add stays 16-bit (no regression for the common case).
7470        let lo = encoder
7471            .encode(&ArmOp::Add {
7472                rd: Reg::R1,
7473                rn: Reg::R2,
7474                op2: Operand2::Reg(Reg::R3),
7475            })
7476            .unwrap();
7477        assert_eq!(
7478            lo.len(),
7479            2,
7480            "low-reg ADD should remain 16-bit, got {lo:02X?}"
7481        );
7482    }
7483
7484    /// #178/#180 sibling: i64 low-word `Adds`/`Subs` can land in R8-R11 pairs;
7485    /// those must fall back to 32-bit ADDS.W/SUBS.W (flag-setting preserved).
7486    #[test]
7487    fn test_encode_thumb_adds_subs_high_reg_use_32bit_178_180() {
7488        let encoder = ArmEncoder::new_thumb2();
7489
7490        // adds r10, r10, r8  → ADDS.W = EB1A 0A08
7491        let adds = encoder
7492            .encode(&ArmOp::Adds {
7493                rd: Reg::R10,
7494                rn: Reg::R10,
7495                op2: Operand2::Reg(Reg::R8),
7496            })
7497            .unwrap();
7498        assert_eq!(
7499            adds,
7500            vec![0x1A, 0xEB, 0x08, 0x0A],
7501            "high-reg ADDS must be 32-bit ADDS.W (EB1A 0A08); got {adds:02X?}"
7502        );
7503
7504        // subs r10, r10, r8  → SUBS.W = EBBA 0A08
7505        let subs = encoder
7506            .encode(&ArmOp::Subs {
7507                rd: Reg::R10,
7508                rn: Reg::R10,
7509                op2: Operand2::Reg(Reg::R8),
7510            })
7511            .unwrap();
7512        assert_eq!(
7513            subs,
7514            vec![0xBA, 0xEB, 0x08, 0x0A],
7515            "high-reg SUBS must be 32-bit SUBS.W (EBBA 0A08); got {subs:02X?}"
7516        );
7517    }
7518
7519    /// #184 (sibling of #180): 16-bit CMN (T1) only encodes R0-R7. High registers
7520    /// must use 32-bit CMN.W, not the corrupt truncated 16-bit form.
7521    #[test]
7522    fn test_encode_thumb_cmn_high_reg_uses_cmn_w_184() {
7523        let encoder = ArmEncoder::new_thumb2();
7524
7525        // cmn r10, r8  → CMN.W = EB1A 0F08 (ADD.W S=1, Rd=PC discarded).
7526        let cmn = encoder
7527            .encode(&ArmOp::Cmn {
7528                rn: Reg::R10,
7529                op2: Operand2::Reg(Reg::R8),
7530            })
7531            .unwrap();
7532        assert_eq!(
7533            cmn,
7534            vec![0x1A, 0xEB, 0x08, 0x0F],
7535            "high-reg CMN must be 32-bit CMN.W (EB1A 0F08); got {cmn:02X?}"
7536        );
7537
7538        // Low registers stay 16-bit: cmn r1, r2 = 0x42D1.
7539        let lo = encoder
7540            .encode(&ArmOp::Cmn {
7541                rn: Reg::R1,
7542                op2: Operand2::Reg(Reg::R2),
7543            })
7544            .unwrap();
7545        assert_eq!(
7546            lo.len(),
7547            2,
7548            "low-reg CMN should remain 16-bit, got {lo:02X?}"
7549        );
7550        assert_eq!(lo, vec![0xD1, 0x42], "low-reg CMN bytes wrong: {lo:02X?}");
7551    }
7552
7553    /// #185 regression: feeding PC (R15) as a data operand to a Thumb-2 op that
7554    /// guards its registers must return Err, not panic under debug-assertions.
7555    /// (Synth never emits PC here; the fuzz harness requires encode() be total.)
7556    #[test]
7557    fn test_encode_pc_operand_returns_err_not_panic_185() {
7558        let encoder = ArmEncoder::new_thumb2();
7559        for op in [
7560            ArmOp::Sdiv {
7561                rd: Reg::PC,
7562                rn: Reg::R0,
7563                rm: Reg::R1,
7564            },
7565            ArmOp::Udiv {
7566                rd: Reg::R0,
7567                rn: Reg::PC,
7568                rm: Reg::R1,
7569            },
7570            ArmOp::Sdiv {
7571                rd: Reg::R0,
7572                rn: Reg::R1,
7573                rm: Reg::PC,
7574            },
7575        ] {
7576            let r = encoder.encode(&op);
7577            assert!(
7578                r.is_err(),
7579                "encode({op:?}) must return Err for a PC operand, got {r:?}"
7580            );
7581        }
7582        // Valid registers still encode fine (no false rejection).
7583        assert!(
7584            encoder
7585                .encode(&ArmOp::Sdiv {
7586                    rd: Reg::R0,
7587                    rn: Reg::R1,
7588                    rm: Reg::R2
7589                })
7590                .is_ok()
7591        );
7592    }
7593
7594    #[test]
7595    fn test_encode_nop_arm32() {
7596        let encoder = ArmEncoder::new_arm32();
7597        let code = encoder.encode(&ArmOp::Nop).unwrap();
7598
7599        assert_eq!(code.len(), 4); // ARM32 instructions are 4 bytes
7600        assert_eq!(code, vec![0x00, 0x00, 0xA0, 0xE1]); // MOV R0, R0
7601    }
7602
7603    #[test]
7604    fn test_encode_nop_thumb() {
7605        let encoder = ArmEncoder::new_thumb2();
7606        let code = encoder.encode(&ArmOp::Nop).unwrap();
7607
7608        assert_eq!(code.len(), 2); // Thumb instructions are 2 bytes
7609        assert_eq!(code, vec![0x00, 0xBF]); // NOP
7610    }
7611
7612    #[test]
7613    fn test_encode_mov_immediate_arm32() {
7614        let encoder = ArmEncoder::new_arm32();
7615        let op = ArmOp::Mov {
7616            rd: Reg::R0,
7617            op2: Operand2::Imm(42),
7618        };
7619
7620        let code = encoder.encode(&op).unwrap();
7621        assert_eq!(code.len(), 4);
7622
7623        // Verify it's a MOV instruction (bits should have immediate flag set)
7624        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
7625        assert_eq!(instr & 0x0E000000, 0x02000000); // Check I bit is set
7626    }
7627
7628    #[test]
7629    fn test_encode_add_registers_arm32() {
7630        let encoder = ArmEncoder::new_arm32();
7631        let op = ArmOp::Add {
7632            rd: Reg::R0,
7633            rn: Reg::R1,
7634            op2: Operand2::Reg(Reg::R2),
7635        };
7636
7637        let code = encoder.encode(&op).unwrap();
7638        assert_eq!(code.len(), 4);
7639
7640        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
7641        // Verify it's an ADD instruction with correct opcode
7642        assert_eq!(instr & 0x0FE00000, 0x00800000);
7643    }
7644
7645    #[test]
7646    fn test_encode_ldr_arm32() {
7647        let encoder = ArmEncoder::new_arm32();
7648        let op = ArmOp::Ldr {
7649            rd: Reg::R0,
7650            addr: MemAddr::imm(Reg::R1, 4),
7651        };
7652
7653        let code = encoder.encode(&op).unwrap();
7654        assert_eq!(code.len(), 4);
7655
7656        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
7657        // Verify load bit is set
7658        assert_eq!(instr & 0x00100000, 0x00100000);
7659    }
7660
7661    #[test]
7662    fn test_encode_str_arm32() {
7663        let encoder = ArmEncoder::new_arm32();
7664        let op = ArmOp::Str {
7665            rd: Reg::R0,
7666            addr: MemAddr::imm(Reg::SP, 0),
7667        };
7668
7669        let code = encoder.encode(&op).unwrap();
7670        assert_eq!(code.len(), 4);
7671    }
7672
7673    #[test]
7674    fn test_encode_branch_arm32() {
7675        let encoder = ArmEncoder::new_arm32();
7676        let op = ArmOp::Bl {
7677            label: "main".to_string(),
7678        };
7679
7680        let code = encoder.encode(&op).unwrap();
7681        assert_eq!(code.len(), 4);
7682
7683        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
7684        // Verify BL opcode
7685        assert_eq!(instr & 0x0F000000, 0x0B000000);
7686    }
7687
7688    /// Regression test for #167 + #174: the Thumb-2 BL relocatable placeholder
7689    /// must carry a -4 addend so an R_ARM_THM_CALL nets to exactly the symbol S.
7690    /// The correct encoding is what `gas` emits for `bl <extern>`: f7ff fffe
7691    /// (hw1=0xF7FF, hw2=0xFFFE), little-endian bytes FF F7 FE FF.
7692    ///   - 0xD000 (J1=J2=0) → ~+0x600000 garbage addend: `bl c0000c` / truncated
7693    ///     to fit (#167).
7694    ///   - 0xF800 (addend 0) → lands at S+4, one instruction past the callee
7695    ///     entry (#174).
7696    ///   - 0xFFFE (addend -4) → lands at S. Correct.
7697    #[test]
7698    fn test_encode_thumb_bl_placeholder_addend_167_174() {
7699        let encoder = ArmEncoder::new_thumb2();
7700        let op = ArmOp::Bl {
7701            label: "callee".to_string(),
7702        };
7703
7704        let code = encoder.encode(&op).unwrap();
7705        assert_eq!(code.len(), 4, "Thumb-2 BL is 32-bit");
7706
7707        let hw1 = u16::from_le_bytes([code[0], code[1]]);
7708        let hw2 = u16::from_le_bytes([code[2], code[3]]);
7709        assert_eq!(hw1, 0xF7FF, "BL first halfword (matches gas `bl <extern>`)");
7710        assert_eq!(
7711            hw2, 0xFFFE,
7712            "BL second halfword must be 0xFFFE (-4 addend → nets to S), not 0xF800 (→ S+4, #174) or 0xD000 (#167)"
7713        );
7714        assert_ne!(hw2, 0xF800, "0xF800 (addend 0) lands at S+4 (#174)");
7715        assert_ne!(hw2, 0xD000, "0xD000 bakes in a ~+0x600000 addend (#167)");
7716    }
7717
7718    #[test]
7719    fn test_encode_sequence() {
7720        let encoder = ArmEncoder::new_arm32();
7721        let ops = vec![
7722            ArmOp::Mov {
7723                rd: Reg::R0,
7724                op2: Operand2::Imm(42),
7725            },
7726            ArmOp::Mov {
7727                rd: Reg::R1,
7728                op2: Operand2::Imm(10),
7729            },
7730            ArmOp::Add {
7731                rd: Reg::R2,
7732                rn: Reg::R0,
7733                op2: Operand2::Reg(Reg::R1),
7734            },
7735        ];
7736
7737        let code = encoder.encode_sequence(&ops).unwrap();
7738        assert_eq!(code.len(), 12); // 3 instructions * 4 bytes
7739    }
7740
7741    #[test]
7742    fn test_reg_to_bits() {
7743        assert_eq!(reg_to_bits(&Reg::R0), 0);
7744        assert_eq!(reg_to_bits(&Reg::R7), 7);
7745        assert_eq!(reg_to_bits(&Reg::SP), 13);
7746        assert_eq!(reg_to_bits(&Reg::LR), 14);
7747        assert_eq!(reg_to_bits(&Reg::PC), 15);
7748    }
7749
7750    #[test]
7751    fn test_encode_bitwise_operations() {
7752        let encoder = ArmEncoder::new_arm32();
7753
7754        let and_op = ArmOp::And {
7755            rd: Reg::R0,
7756            rn: Reg::R1,
7757            op2: Operand2::Reg(Reg::R2),
7758        };
7759        let and_code = encoder.encode(&and_op).unwrap();
7760        assert_eq!(and_code.len(), 4);
7761
7762        let orr_op = ArmOp::Orr {
7763            rd: Reg::R0,
7764            rn: Reg::R1,
7765            op2: Operand2::Reg(Reg::R2),
7766        };
7767        let orr_code = encoder.encode(&orr_op).unwrap();
7768        assert_eq!(orr_code.len(), 4);
7769
7770        let eor_op = ArmOp::Eor {
7771            rd: Reg::R0,
7772            rn: Reg::R1,
7773            op2: Operand2::Reg(Reg::R2),
7774        };
7775        let eor_code = encoder.encode(&eor_op).unwrap();
7776        assert_eq!(eor_code.len(), 4);
7777    }
7778
7779    // === Thumb-2 32-bit encoding tests ===
7780
7781    #[test]
7782    fn test_encode_sdiv_thumb2() {
7783        let encoder = ArmEncoder::new_thumb2();
7784        let op = ArmOp::Sdiv {
7785            rd: Reg::R0,
7786            rn: Reg::R1,
7787            rm: Reg::R2,
7788        };
7789
7790        let code = encoder.encode(&op).unwrap();
7791        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
7792
7793        // SDIV R0, R1, R2: 0xFB91 0xF0F2
7794        // First halfword: 0xFB90 | Rn(1) = 0xFB91
7795        // Second halfword: 0xF0F0 | Rd(0)<<8 | Rm(2) = 0xF0F2
7796        // Little-endian: [0x91, 0xFB, 0xF2, 0xF0]
7797        assert_eq!(code[0], 0x91);
7798        assert_eq!(code[1], 0xFB);
7799        assert_eq!(code[2], 0xF2);
7800        assert_eq!(code[3], 0xF0);
7801    }
7802
7803    #[test]
7804    fn test_encode_udiv_thumb2() {
7805        let encoder = ArmEncoder::new_thumb2();
7806        let op = ArmOp::Udiv {
7807            rd: Reg::R0,
7808            rn: Reg::R1,
7809            rm: Reg::R2,
7810        };
7811
7812        let code = encoder.encode(&op).unwrap();
7813        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
7814
7815        // UDIV R0, R1, R2: 0xFBB1 0xF0F2
7816        // Little-endian: [0xB1, 0xFB, 0xF2, 0xF0]
7817        assert_eq!(code[0], 0xB1);
7818        assert_eq!(code[1], 0xFB);
7819        assert_eq!(code[2], 0xF2);
7820        assert_eq!(code[3], 0xF0);
7821    }
7822
7823    #[test]
7824    fn test_encode_mul_thumb2() {
7825        let encoder = ArmEncoder::new_thumb2();
7826        let op = ArmOp::Mul {
7827            rd: Reg::R0,
7828            rn: Reg::R1,
7829            rm: Reg::R2,
7830        };
7831
7832        let code = encoder.encode(&op).unwrap();
7833        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
7834    }
7835
7836    #[test]
7837    fn test_encode_and_thumb2() {
7838        let encoder = ArmEncoder::new_thumb2();
7839        let op = ArmOp::And {
7840            rd: Reg::R0,
7841            rn: Reg::R1,
7842            op2: Operand2::Reg(Reg::R2),
7843        };
7844
7845        let code = encoder.encode(&op).unwrap();
7846        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
7847    }
7848
7849    #[test]
7850    fn test_encode_lsl_thumb2_low_regs() {
7851        let encoder = ArmEncoder::new_thumb2();
7852        let op = ArmOp::Lsl {
7853            rd: Reg::R0,
7854            rn: Reg::R1,
7855            shift: 5,
7856        };
7857
7858        let code = encoder.encode(&op).unwrap();
7859        assert_eq!(code.len(), 2); // 16-bit for low registers
7860    }
7861
7862    #[test]
7863    fn test_encode_clz_thumb2() {
7864        let encoder = ArmEncoder::new_thumb2();
7865        let op = ArmOp::Clz {
7866            rd: Reg::R0,
7867            rm: Reg::R1,
7868        };
7869
7870        let code = encoder.encode(&op).unwrap();
7871        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
7872    }
7873
7874    #[test]
7875    fn test_encode_bx_thumb2() {
7876        let encoder = ArmEncoder::new_thumb2();
7877        let op = ArmOp::Bx { rm: Reg::LR };
7878
7879        let code = encoder.encode(&op).unwrap();
7880        assert_eq!(code.len(), 2); // 16-bit instruction
7881
7882        // BX LR: 0x4770
7883        assert_eq!(code, vec![0x70, 0x47]);
7884    }
7885
7886    // ========================================================================
7887    // f32 pseudo-op encoding tests
7888    // ========================================================================
7889
7890    #[test]
7891    fn test_encode_f32_abs_arm32() {
7892        let encoder = ArmEncoder::new_arm32();
7893        let op = ArmOp::F32Abs {
7894            sd: VfpReg::S0,
7895            sm: VfpReg::S2,
7896        };
7897        let code = encoder.encode(&op).unwrap();
7898        assert_eq!(code.len(), 4); // Single VFP instruction
7899    }
7900
7901    #[test]
7902    fn test_encode_f32_neg_arm32() {
7903        let encoder = ArmEncoder::new_arm32();
7904        let op = ArmOp::F32Neg {
7905            sd: VfpReg::S0,
7906            sm: VfpReg::S2,
7907        };
7908        let code = encoder.encode(&op).unwrap();
7909        assert_eq!(code.len(), 4);
7910    }
7911
7912    #[test]
7913    fn test_encode_f32_sqrt_arm32() {
7914        let encoder = ArmEncoder::new_arm32();
7915        let op = ArmOp::F32Sqrt {
7916            sd: VfpReg::S0,
7917            sm: VfpReg::S2,
7918        };
7919        let code = encoder.encode(&op).unwrap();
7920        assert_eq!(code.len(), 4);
7921    }
7922
7923    #[test]
7924    fn test_encode_f32_ceil_arm32() {
7925        let encoder = ArmEncoder::new_arm32();
7926        let op = ArmOp::F32Ceil {
7927            sd: VfpReg::S0,
7928            sm: VfpReg::S2,
7929        };
7930        let code = encoder.encode(&op).unwrap();
7931        // VMRS + BIC + ORR + VMSR + VCVT.S32.F32 + VMRS + BIC + VMSR + VCVT.F32.S32
7932        assert_eq!(code.len(), 36);
7933    }
7934
7935    #[test]
7936    fn test_encode_f32_floor_thumb2() {
7937        let encoder = ArmEncoder::new_thumb2();
7938        let op = ArmOp::F32Floor {
7939            sd: VfpReg::S0,
7940            sm: VfpReg::S2,
7941        };
7942        let code = encoder.encode(&op).unwrap();
7943        // VMRS + BIC.W + ORR.W + VMSR + VCVT + VMRS + BIC.W + VMSR + VCVT.F32.S32
7944        assert_eq!(code.len(), 36);
7945    }
7946
7947    #[test]
7948    fn test_encode_f32_min_arm32() {
7949        let encoder = ArmEncoder::new_arm32();
7950        let op = ArmOp::F32Min {
7951            sd: VfpReg::S0,
7952            sn: VfpReg::S2,
7953            sm: VfpReg::S4,
7954        };
7955        let code = encoder.encode(&op).unwrap();
7956        assert_eq!(code.len(), 16); // VMOV + VCMP + VMRS + conditional VMOV
7957    }
7958
7959    #[test]
7960    fn test_encode_f32_max_thumb2() {
7961        let encoder = ArmEncoder::new_thumb2();
7962        let op = ArmOp::F32Max {
7963            sd: VfpReg::S0,
7964            sn: VfpReg::S2,
7965            sm: VfpReg::S4,
7966        };
7967        let code = encoder.encode(&op).unwrap();
7968        // VMOV(4) + VCMP(4) + VMRS(4) + IT(2) + VMOV(4) = 18
7969        assert_eq!(code.len(), 18);
7970    }
7971
7972    #[test]
7973    fn test_encode_f32_copysign_arm32() {
7974        let encoder = ArmEncoder::new_arm32();
7975        let op = ArmOp::F32Copysign {
7976            sd: VfpReg::S0,
7977            sn: VfpReg::S2,
7978            sm: VfpReg::S4,
7979        };
7980        let code = encoder.encode(&op).unwrap();
7981        // VMOV + VMOV + AND + BIC + ORR + VMOV = 6 * 4 = 24
7982        assert_eq!(code.len(), 24);
7983    }
7984
7985    // ========================================================================
7986    // f64 encoding tests
7987    // ========================================================================
7988
7989    #[test]
7990    fn test_encode_f64_add_arm32() {
7991        let encoder = ArmEncoder::new_arm32();
7992        let op = ArmOp::F64Add {
7993            dd: VfpReg::D0,
7994            dn: VfpReg::D1,
7995            dm: VfpReg::D2,
7996        };
7997        let code = encoder.encode(&op).unwrap();
7998        assert_eq!(code.len(), 4);
7999        // VADD.F64 D0, D1, D2: check coprocessor is cp11 (0xB)
8000        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8001        assert_eq!((instr >> 8) & 0xF, 0xB); // cp11
8002    }
8003
8004    #[test]
8005    fn test_encode_f64_sub_thumb2() {
8006        let encoder = ArmEncoder::new_thumb2();
8007        let op = ArmOp::F64Sub {
8008            dd: VfpReg::D0,
8009            dn: VfpReg::D1,
8010            dm: VfpReg::D2,
8011        };
8012        let code = encoder.encode(&op).unwrap();
8013        assert_eq!(code.len(), 4); // 32-bit VFP as two Thumb halfwords
8014    }
8015
8016    #[test]
8017    fn test_encode_f64_mul_arm32() {
8018        let encoder = ArmEncoder::new_arm32();
8019        let op = ArmOp::F64Mul {
8020            dd: VfpReg::D0,
8021            dn: VfpReg::D1,
8022            dm: VfpReg::D2,
8023        };
8024        let code = encoder.encode(&op).unwrap();
8025        assert_eq!(code.len(), 4);
8026    }
8027
8028    #[test]
8029    fn test_encode_f64_div_arm32() {
8030        let encoder = ArmEncoder::new_arm32();
8031        let op = ArmOp::F64Div {
8032            dd: VfpReg::D0,
8033            dn: VfpReg::D1,
8034            dm: VfpReg::D2,
8035        };
8036        let code = encoder.encode(&op).unwrap();
8037        assert_eq!(code.len(), 4);
8038    }
8039
8040    #[test]
8041    fn test_encode_f64_abs_arm32() {
8042        let encoder = ArmEncoder::new_arm32();
8043        let op = ArmOp::F64Abs {
8044            dd: VfpReg::D0,
8045            dm: VfpReg::D2,
8046        };
8047        let code = encoder.encode(&op).unwrap();
8048        assert_eq!(code.len(), 4);
8049    }
8050
8051    #[test]
8052    fn test_encode_f64_neg_arm32() {
8053        let encoder = ArmEncoder::new_arm32();
8054        let op = ArmOp::F64Neg {
8055            dd: VfpReg::D0,
8056            dm: VfpReg::D2,
8057        };
8058        let code = encoder.encode(&op).unwrap();
8059        assert_eq!(code.len(), 4);
8060    }
8061
8062    #[test]
8063    fn test_encode_f64_sqrt_arm32() {
8064        let encoder = ArmEncoder::new_arm32();
8065        let op = ArmOp::F64Sqrt {
8066            dd: VfpReg::D0,
8067            dm: VfpReg::D2,
8068        };
8069        let code = encoder.encode(&op).unwrap();
8070        assert_eq!(code.len(), 4);
8071    }
8072
8073    #[test]
8074    fn test_encode_f64_load_arm32() {
8075        let encoder = ArmEncoder::new_arm32();
8076        let op = ArmOp::F64Load {
8077            dd: VfpReg::D0,
8078            addr: MemAddr::imm(Reg::R0, 8),
8079        };
8080        let code = encoder.encode(&op).unwrap();
8081        assert_eq!(code.len(), 4);
8082        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8083        assert_eq!((instr >> 8) & 0xF, 0xB); // cp11 for F64
8084        assert_eq!(instr & 0xFF, 2); // offset 8 / 4 = 2
8085    }
8086
8087    #[test]
8088    fn test_encode_f64_store_thumb2() {
8089        let encoder = ArmEncoder::new_thumb2();
8090        let op = ArmOp::F64Store {
8091            dd: VfpReg::D0,
8092            addr: MemAddr::imm(Reg::SP, 0),
8093        };
8094        let code = encoder.encode(&op).unwrap();
8095        assert_eq!(code.len(), 4);
8096    }
8097
8098    #[test]
8099    fn test_encode_f64_compare_arm32() {
8100        let encoder = ArmEncoder::new_arm32();
8101        let op = ArmOp::F64Eq {
8102            rd: Reg::R0,
8103            dn: VfpReg::D0,
8104            dm: VfpReg::D1,
8105        };
8106        let code = encoder.encode(&op).unwrap();
8107        assert_eq!(code.len(), 16); // VCMP + VMRS + MOV #0 + MOVcond #1
8108    }
8109
8110    #[test]
8111    fn test_encode_f64_compare_thumb2() {
8112        let encoder = ArmEncoder::new_thumb2();
8113        let op = ArmOp::F64Lt {
8114            rd: Reg::R0,
8115            dn: VfpReg::D0,
8116            dm: VfpReg::D1,
8117        };
8118        let code = encoder.encode(&op).unwrap();
8119        // VCMP(4) + VMRS(4) + MOVS(2) + IT(2) + MOV(2) = 14
8120        assert_eq!(code.len(), 14);
8121    }
8122
8123    #[test]
8124    fn test_encode_f64_const_arm32() {
8125        let encoder = ArmEncoder::new_arm32();
8126        let op = ArmOp::F64Const {
8127            dd: VfpReg::D0,
8128            value: 3.125,
8129        };
8130        let code = encoder.encode(&op).unwrap();
8131        // MOVW(4) + MOVT(4) + MOVW(4) + MOVT(4) + VMOV(4) = 20
8132        assert_eq!(code.len(), 20);
8133    }
8134
8135    #[test]
8136    fn test_encode_f64_const_thumb2() {
8137        let encoder = ArmEncoder::new_thumb2();
8138        let op = ArmOp::F64Const {
8139            dd: VfpReg::D0,
8140            value: 2.5,
8141        };
8142        let code = encoder.encode(&op).unwrap();
8143        // MOVW(4) + MOVT(4) + MOVW(4) + MOVT(4) + VMOV(4) = 20
8144        assert_eq!(code.len(), 20);
8145    }
8146
8147    #[test]
8148    fn test_encode_f64_convert_i32s_arm32() {
8149        let encoder = ArmEncoder::new_arm32();
8150        let op = ArmOp::F64ConvertI32S {
8151            dd: VfpReg::D0,
8152            rm: Reg::R0,
8153        };
8154        let code = encoder.encode(&op).unwrap();
8155        // VMOV(4) + VCVT(4) = 8
8156        assert_eq!(code.len(), 8);
8157    }
8158
8159    #[test]
8160    fn test_encode_f64_promote_f32_arm32() {
8161        let encoder = ArmEncoder::new_arm32();
8162        let op = ArmOp::F64PromoteF32 {
8163            dd: VfpReg::D0,
8164            sm: VfpReg::S0,
8165        };
8166        let code = encoder.encode(&op).unwrap();
8167        assert_eq!(code.len(), 4); // Single VCVT.F64.F32 instruction
8168    }
8169
8170    #[test]
8171    fn test_encode_f64_promote_f32_thumb2() {
8172        let encoder = ArmEncoder::new_thumb2();
8173        let op = ArmOp::F64PromoteF32 {
8174            dd: VfpReg::D0,
8175            sm: VfpReg::S0,
8176        };
8177        let code = encoder.encode(&op).unwrap();
8178        assert_eq!(code.len(), 4);
8179    }
8180
8181    #[test]
8182    fn test_encode_i32_trunc_f64s_arm32() {
8183        let encoder = ArmEncoder::new_arm32();
8184        let op = ArmOp::I32TruncF64S {
8185            rd: Reg::R0,
8186            dm: VfpReg::D0,
8187        };
8188        let code = encoder.encode(&op).unwrap();
8189        // VCVT(4) + VMOV(4) = 8
8190        assert_eq!(code.len(), 8);
8191    }
8192
8193    #[test]
8194    fn test_encode_f64_reinterpret_i64_arm32() {
8195        let encoder = ArmEncoder::new_arm32();
8196        let op = ArmOp::F64ReinterpretI64 {
8197            dd: VfpReg::D0,
8198            rmlo: Reg::R0,
8199            rmhi: Reg::R1,
8200        };
8201        let code = encoder.encode(&op).unwrap();
8202        assert_eq!(code.len(), 4); // Single VMOV instruction
8203    }
8204
8205    #[test]
8206    fn test_encode_i64_reinterpret_f64_thumb2() {
8207        let encoder = ArmEncoder::new_thumb2();
8208        let op = ArmOp::I64ReinterpretF64 {
8209            rdlo: Reg::R0,
8210            rdhi: Reg::R1,
8211            dm: VfpReg::D0,
8212        };
8213        let code = encoder.encode(&op).unwrap();
8214        assert_eq!(code.len(), 4);
8215    }
8216
8217    #[test]
8218    fn test_encode_f64_trunc_thumb2() {
8219        let encoder = ArmEncoder::new_thumb2();
8220        let op = ArmOp::F64Trunc {
8221            dd: VfpReg::D0,
8222            dm: VfpReg::D1,
8223        };
8224        let code = encoder.encode(&op).unwrap();
8225        // Two VFP instructions via Thumb encoding
8226        assert_eq!(code.len(), 8);
8227    }
8228
8229    #[test]
8230    fn test_encode_f64_min_arm32() {
8231        let encoder = ArmEncoder::new_arm32();
8232        let op = ArmOp::F64Min {
8233            dd: VfpReg::D0,
8234            dn: VfpReg::D1,
8235            dm: VfpReg::D2,
8236        };
8237        let code = encoder.encode(&op).unwrap();
8238        // VMOV + VCMP + VMRS + conditional VMOV = 16
8239        assert_eq!(code.len(), 16);
8240    }
8241
8242    #[test]
8243    fn test_f64_cp11_encoding() {
8244        // Verify that F64 instructions use coprocessor 11 (0xB), not 10 (0xA)
8245        let encoder = ArmEncoder::new_arm32();
8246
8247        // F64Add
8248        let code = encoder
8249            .encode(&ArmOp::F64Add {
8250                dd: VfpReg::D0,
8251                dn: VfpReg::D0,
8252                dm: VfpReg::D0,
8253            })
8254            .unwrap();
8255        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8256        assert_eq!((instr >> 8) & 0xF, 0xB, "F64 should use cp11");
8257
8258        // F32Add for comparison
8259        let code = encoder
8260            .encode(&ArmOp::F32Add {
8261                sd: VfpReg::S0,
8262                sn: VfpReg::S0,
8263                sm: VfpReg::S0,
8264            })
8265            .unwrap();
8266        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8267        assert_eq!((instr >> 8) & 0xF, 0xA, "F32 should use cp10");
8268    }
8269
8270    #[test]
8271    fn test_dreg_encoding_higher_registers() {
8272        let encoder = ArmEncoder::new_arm32();
8273
8274        // Test with D15 (highest register)
8275        let op = ArmOp::F64Add {
8276            dd: VfpReg::D15,
8277            dn: VfpReg::D14,
8278            dm: VfpReg::D13,
8279        };
8280        let code = encoder.encode(&op).unwrap();
8281        assert_eq!(code.len(), 4);
8282
8283        // Verify the register encoding worked (instruction is valid)
8284        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8285        assert_eq!((instr >> 8) & 0xF, 0xB); // cp11
8286    }
8287
8288    // ========================================================================
8289    // Control flow encoding tests
8290    // ========================================================================
8291
8292    #[test]
8293    fn test_encode_label_emits_no_bytes() {
8294        let encoder = ArmEncoder::new_thumb2();
8295        let op = ArmOp::Label {
8296            name: ".Lblock_end_0".to_string(),
8297        };
8298        let code = encoder.encode(&op).unwrap();
8299        assert!(code.is_empty(), "Label should emit zero bytes");
8300
8301        let encoder32 = ArmEncoder::new_arm32();
8302        let code32 = encoder32.encode(&op).unwrap();
8303        assert!(
8304            code32.is_empty(),
8305            "Label should emit zero bytes in ARM32 too"
8306        );
8307    }
8308
8309    #[test]
8310    fn test_encode_bcc_eq_thumb2() {
8311        use synth_synthesis::Condition;
8312        let encoder = ArmEncoder::new_thumb2();
8313        let op = ArmOp::Bcc {
8314            cond: Condition::EQ,
8315            label: "target".to_string(),
8316        };
8317        let code = encoder.encode(&op).unwrap();
8318        assert_eq!(code.len(), 2); // 16-bit conditional branch
8319
8320        // BEQ with offset 0: 0xD000 in little-endian
8321        assert_eq!(code, vec![0x00, 0xD0]);
8322    }
8323
8324    #[test]
8325    fn test_encode_bcc_ne_thumb2() {
8326        use synth_synthesis::Condition;
8327        let encoder = ArmEncoder::new_thumb2();
8328        let op = ArmOp::Bcc {
8329            cond: Condition::NE,
8330            label: "target".to_string(),
8331        };
8332        let code = encoder.encode(&op).unwrap();
8333        assert_eq!(code.len(), 2);
8334
8335        // BNE with offset 0: 0xD100 in little-endian
8336        assert_eq!(code, vec![0x00, 0xD1]);
8337    }
8338
8339    #[test]
8340    fn test_encode_bcc_arm32() {
8341        use synth_synthesis::Condition;
8342        let encoder = ArmEncoder::new_arm32();
8343        let op = ArmOp::Bcc {
8344            cond: Condition::EQ,
8345            label: "target".to_string(),
8346        };
8347        let code = encoder.encode(&op).unwrap();
8348        assert_eq!(code.len(), 4); // 32-bit ARM instruction
8349
8350        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8351        // BEQ: cond=0x0, opcode=0xA, offset=0
8352        assert_eq!(instr & 0xF0000000, 0x00000000); // EQ condition
8353        assert_eq!(instr & 0x0F000000, 0x0A000000); // Branch opcode
8354    }
8355
8356    #[test]
8357    fn test_encode_udf_thumb2() {
8358        let encoder = ArmEncoder::new_thumb2();
8359        let op = ArmOp::Udf { imm: 0 };
8360        let code = encoder.encode(&op).unwrap();
8361        assert_eq!(code.len(), 2); // 16-bit
8362
8363        // UDF #0: 0xDE00 in little-endian
8364        assert_eq!(code, vec![0x00, 0xDE]);
8365    }
8366
8367    #[test]
8368    fn test_encode_nop_thumb2() {
8369        let encoder = ArmEncoder::new_thumb2();
8370        let op = ArmOp::Nop;
8371        let code = encoder.encode(&op).unwrap();
8372        assert_eq!(code.len(), 2); // 16-bit
8373
8374        // NOP: 0xBF00 in little-endian
8375        assert_eq!(code, vec![0x00, 0xBF]);
8376    }
8377
8378    // =========================================================================
8379    // i64 Thumb-2 encoding tests
8380    // =========================================================================
8381
8382    #[test]
8383    fn test_encode_i64_add_thumb2() {
8384        let encoder = ArmEncoder::new_thumb2();
8385        let op = ArmOp::I64Add {
8386            rdlo: Reg::R0,
8387            rdhi: Reg::R1,
8388            rnlo: Reg::R0,
8389            rnhi: Reg::R1,
8390            rmlo: Reg::R2,
8391            rmhi: Reg::R3,
8392        };
8393        let code = encoder.encode(&op).unwrap();
8394        // Should emit ADDS (2 bytes) + ADC.W (4 bytes) = 6 bytes
8395        assert_eq!(code.len(), 6, "I64Add should be 6 bytes (ADDS + ADC.W)");
8396    }
8397
8398    #[test]
8399    fn test_encode_i64_sub_thumb2() {
8400        let encoder = ArmEncoder::new_thumb2();
8401        let op = ArmOp::I64Sub {
8402            rdlo: Reg::R0,
8403            rdhi: Reg::R1,
8404            rnlo: Reg::R0,
8405            rnhi: Reg::R1,
8406            rmlo: Reg::R2,
8407            rmhi: Reg::R3,
8408        };
8409        let code = encoder.encode(&op).unwrap();
8410        // Should emit SUBS (2 bytes) + SBC.W (4 bytes) = 6 bytes
8411        assert_eq!(code.len(), 6, "I64Sub should be 6 bytes (SUBS + SBC.W)");
8412    }
8413
8414    #[test]
8415    fn test_encode_i64_and_thumb2() {
8416        let encoder = ArmEncoder::new_thumb2();
8417        let op = ArmOp::I64And {
8418            rdlo: Reg::R0,
8419            rdhi: Reg::R1,
8420            rnlo: Reg::R0,
8421            rnhi: Reg::R1,
8422            rmlo: Reg::R2,
8423            rmhi: Reg::R3,
8424        };
8425        let code = encoder.encode(&op).unwrap();
8426        // AND.W (4 bytes) + AND.W (4 bytes) = 8 bytes
8427        assert!(code.len() >= 4, "I64And should emit at least 4 bytes");
8428    }
8429
8430    #[test]
8431    fn test_encode_i64_or_thumb2() {
8432        let encoder = ArmEncoder::new_thumb2();
8433        let op = ArmOp::I64Or {
8434            rdlo: Reg::R0,
8435            rdhi: Reg::R1,
8436            rnlo: Reg::R0,
8437            rnhi: Reg::R1,
8438            rmlo: Reg::R2,
8439            rmhi: Reg::R3,
8440        };
8441        let code = encoder.encode(&op).unwrap();
8442        assert!(code.len() >= 4, "I64Or should emit at least 4 bytes");
8443    }
8444
8445    #[test]
8446    fn test_encode_i64_xor_thumb2() {
8447        let encoder = ArmEncoder::new_thumb2();
8448        let op = ArmOp::I64Xor {
8449            rdlo: Reg::R0,
8450            rdhi: Reg::R1,
8451            rnlo: Reg::R0,
8452            rnhi: Reg::R1,
8453            rmlo: Reg::R2,
8454            rmhi: Reg::R3,
8455        };
8456        let code = encoder.encode(&op).unwrap();
8457        assert!(code.len() >= 4, "I64Xor should emit at least 4 bytes");
8458    }
8459
8460    #[test]
8461    fn test_encode_i64_const_small_thumb2() {
8462        let encoder = ArmEncoder::new_thumb2();
8463        // Small constant: only needs MOVW for each half
8464        let op = ArmOp::I64Const {
8465            rdlo: Reg::R0,
8466            rdhi: Reg::R1,
8467            value: 42,
8468        };
8469        let code = encoder.encode(&op).unwrap();
8470        // MOVW R0, #42 (4 bytes) + MOVW R1, #0 (4 bytes) = 8 bytes minimum
8471        assert!(code.len() >= 8, "I64Const should emit at least 8 bytes");
8472    }
8473
8474    #[test]
8475    fn test_encode_i64_const_large_thumb2() {
8476        let encoder = ArmEncoder::new_thumb2();
8477        // Large constant: needs MOVW+MOVT for each half
8478        let op = ArmOp::I64Const {
8479            rdlo: Reg::R0,
8480            rdhi: Reg::R1,
8481            value: 0x1234_5678_9ABC_DEF0_u64 as i64,
8482        };
8483        let code = encoder.encode(&op).unwrap();
8484        // MOVW + MOVT for lo (8 bytes) + MOVW + MOVT for hi (8 bytes) = 16 bytes
8485        assert_eq!(
8486            code.len(),
8487            16,
8488            "I64Const with large value should be 16 bytes"
8489        );
8490    }
8491
8492    #[test]
8493    fn test_encode_i64_extend_i32_s_thumb2() {
8494        let encoder = ArmEncoder::new_thumb2();
8495        let op = ArmOp::I64ExtendI32S {
8496            rdlo: Reg::R0,
8497            rdhi: Reg::R1,
8498            rn: Reg::R0,
8499        };
8500        let code = encoder.encode(&op).unwrap();
8501        // When rdlo == rn, only ASR (4 bytes) is emitted
8502        assert_eq!(
8503            code.len(),
8504            4,
8505            "I64ExtendI32S (same reg) should be 4 bytes (ASR only)"
8506        );
8507    }
8508
8509    #[test]
8510    fn test_encode_i64_extend_i32_s_diff_reg_thumb2() {
8511        let encoder = ArmEncoder::new_thumb2();
8512        let op = ArmOp::I64ExtendI32S {
8513            rdlo: Reg::R0,
8514            rdhi: Reg::R1,
8515            rn: Reg::R2,
8516        };
8517        let code = encoder.encode(&op).unwrap();
8518        // MOV rdlo, rn (2 bytes for low regs) + ASR rdhi, rdlo, #31 (4 bytes) = 6 bytes
8519        assert!(
8520            code.len() >= 6,
8521            "I64ExtendI32S (diff reg) should be at least 6 bytes"
8522        );
8523    }
8524
8525    #[test]
8526    fn test_encode_i64_extend_i32_u_thumb2() {
8527        let encoder = ArmEncoder::new_thumb2();
8528        let op = ArmOp::I64ExtendI32U {
8529            rdlo: Reg::R0,
8530            rdhi: Reg::R1,
8531            rn: Reg::R0,
8532        };
8533        let code = encoder.encode(&op).unwrap();
8534        // When rdlo == rn, only MOV rdhi, #0 (2 bytes) is emitted
8535        assert_eq!(
8536            code.len(),
8537            2,
8538            "I64ExtendI32U (same reg) should be 2 bytes (MOV #0 only)"
8539        );
8540    }
8541
8542    #[test]
8543    fn test_encode_i32_wrap_i64_nop_thumb2() {
8544        let encoder = ArmEncoder::new_thumb2();
8545        // When rd == rnlo, should be a NOP
8546        let op = ArmOp::I32WrapI64 {
8547            rd: Reg::R0,
8548            rnlo: Reg::R0,
8549        };
8550        let code = encoder.encode(&op).unwrap();
8551        assert_eq!(code.len(), 2, "I32WrapI64 same reg should be NOP (2 bytes)");
8552        assert_eq!(code, vec![0x00, 0xBF]); // NOP
8553    }
8554
8555    #[test]
8556    fn test_encode_i32_wrap_i64_diff_reg_thumb2() {
8557        let encoder = ArmEncoder::new_thumb2();
8558        let op = ArmOp::I32WrapI64 {
8559            rd: Reg::R2,
8560            rnlo: Reg::R0,
8561        };
8562        let code = encoder.encode(&op).unwrap();
8563        // MOV R2, R0 (2 or 4 bytes)
8564        assert!(
8565            code.len() >= 2,
8566            "I32WrapI64 diff reg should emit at least 2 bytes"
8567        );
8568    }
8569
8570    #[test]
8571    fn test_encode_i64_eqz_thumb2() {
8572        let encoder = ArmEncoder::new_thumb2();
8573        let op = ArmOp::I64Eqz {
8574            rd: Reg::R0,
8575            rnlo: Reg::R0,
8576            rnhi: Reg::R1,
8577        };
8578        let code = encoder.encode(&op).unwrap();
8579        // Delegates to I64SetCondZ which is already encoded
8580        assert!(
8581            code.len() >= 6,
8582            "I64Eqz should emit at least 6 bytes for ORR+ITE+MOV+MOV"
8583        );
8584    }
8585
8586    #[test]
8587    fn test_encode_i64_eq_thumb2() {
8588        let encoder = ArmEncoder::new_thumb2();
8589        let op = ArmOp::I64Eq {
8590            rd: Reg::R0,
8591            rnlo: Reg::R0,
8592            rnhi: Reg::R1,
8593            rmlo: Reg::R2,
8594            rmhi: Reg::R3,
8595        };
8596        let code = encoder.encode(&op).unwrap();
8597        // Delegates to I64SetCond EQ: CMP lo + IT EQ + CMPEQ hi + ITE EQ + MOV 1 + MOV 0
8598        assert!(code.len() >= 10, "I64Eq should emit at least 10 bytes");
8599    }
8600
8601    #[test]
8602    fn test_encode_i64_ldr_thumb2() {
8603        let encoder = ArmEncoder::new_thumb2();
8604        let op = ArmOp::I64Ldr {
8605            rdlo: Reg::R0,
8606            rdhi: Reg::R1,
8607            addr: MemAddr::imm(Reg::SP, 0),
8608        };
8609        let code = encoder.encode(&op).unwrap();
8610        // Two LDR instructions (lo at offset, hi at offset+4)
8611        assert!(code.len() >= 4, "I64Ldr should emit at least 4 bytes");
8612    }
8613
8614    #[test]
8615    fn test_encode_i64_str_thumb2() {
8616        let encoder = ArmEncoder::new_thumb2();
8617        let op = ArmOp::I64Str {
8618            rdlo: Reg::R0,
8619            rdhi: Reg::R1,
8620            addr: MemAddr::imm(Reg::SP, 0),
8621        };
8622        let code = encoder.encode(&op).unwrap();
8623        // Two STR instructions (lo at offset, hi at offset+4)
8624        assert!(code.len() >= 4, "I64Str should emit at least 4 bytes");
8625    }
8626
8627    #[test]
8628    fn test_encode_i64_all_comparisons_thumb2() {
8629        let encoder = ArmEncoder::new_thumb2();
8630
8631        let ops = vec![
8632            ArmOp::I64Ne {
8633                rd: Reg::R0,
8634                rnlo: Reg::R0,
8635                rnhi: Reg::R1,
8636                rmlo: Reg::R2,
8637                rmhi: Reg::R3,
8638            },
8639            ArmOp::I64LtS {
8640                rd: Reg::R0,
8641                rnlo: Reg::R0,
8642                rnhi: Reg::R1,
8643                rmlo: Reg::R2,
8644                rmhi: Reg::R3,
8645            },
8646            ArmOp::I64LtU {
8647                rd: Reg::R0,
8648                rnlo: Reg::R0,
8649                rnhi: Reg::R1,
8650                rmlo: Reg::R2,
8651                rmhi: Reg::R3,
8652            },
8653            ArmOp::I64LeS {
8654                rd: Reg::R0,
8655                rnlo: Reg::R0,
8656                rnhi: Reg::R1,
8657                rmlo: Reg::R2,
8658                rmhi: Reg::R3,
8659            },
8660            ArmOp::I64LeU {
8661                rd: Reg::R0,
8662                rnlo: Reg::R0,
8663                rnhi: Reg::R1,
8664                rmlo: Reg::R2,
8665                rmhi: Reg::R3,
8666            },
8667            ArmOp::I64GtS {
8668                rd: Reg::R0,
8669                rnlo: Reg::R0,
8670                rnhi: Reg::R1,
8671                rmlo: Reg::R2,
8672                rmhi: Reg::R3,
8673            },
8674            ArmOp::I64GtU {
8675                rd: Reg::R0,
8676                rnlo: Reg::R0,
8677                rnhi: Reg::R1,
8678                rmlo: Reg::R2,
8679                rmhi: Reg::R3,
8680            },
8681            ArmOp::I64GeS {
8682                rd: Reg::R0,
8683                rnlo: Reg::R0,
8684                rnhi: Reg::R1,
8685                rmlo: Reg::R2,
8686                rmhi: Reg::R3,
8687            },
8688            ArmOp::I64GeU {
8689                rd: Reg::R0,
8690                rnlo: Reg::R0,
8691                rnhi: Reg::R1,
8692                rmlo: Reg::R2,
8693                rmhi: Reg::R3,
8694            },
8695        ];
8696
8697        for op in &ops {
8698            let code = encoder.encode(op).unwrap();
8699            assert!(
8700                code.len() >= 8,
8701                "i64 comparison {:?} should emit at least 8 bytes, got {}",
8702                op,
8703                code.len()
8704            );
8705        }
8706    }
8707
8708    #[test]
8709    fn test_encode_i64_const_zero_thumb2() {
8710        let encoder = ArmEncoder::new_thumb2();
8711        let op = ArmOp::I64Const {
8712            rdlo: Reg::R0,
8713            rdhi: Reg::R1,
8714            value: 0,
8715        };
8716        let code = encoder.encode(&op).unwrap();
8717        // MOVW R0, #0 (4 bytes) + MOVW R1, #0 (4 bytes) = 8 bytes
8718        assert_eq!(code.len(), 8, "I64Const(0) should be 8 bytes");
8719    }
8720
8721    #[test]
8722    fn test_encode_i64_const_negative_one_thumb2() {
8723        let encoder = ArmEncoder::new_thumb2();
8724        let op = ArmOp::I64Const {
8725            rdlo: Reg::R0,
8726            rdhi: Reg::R1,
8727            value: -1, // 0xFFFF_FFFF_FFFF_FFFF
8728        };
8729        let code = encoder.encode(&op).unwrap();
8730        // MOVW + MOVT for lo (8 bytes) + MOVW + MOVT for hi (8 bytes) = 16 bytes
8731        assert_eq!(code.len(), 16, "I64Const(-1) should be 16 bytes");
8732    }
8733
8734    // =========================================================================
8735    // Sub-word load/store encoding tests
8736    // =========================================================================
8737
8738    #[test]
8739    fn test_encode_ldrb_arm32() {
8740        let encoder = ArmEncoder::new_arm32();
8741        let op = ArmOp::Ldrb {
8742            rd: Reg::R0,
8743            addr: MemAddr::imm(Reg::R1, 4),
8744        };
8745        let code = encoder.encode(&op).unwrap();
8746        assert_eq!(code.len(), 4, "ARM32 LDRB should be 4 bytes");
8747        // LDRB R0, [R1, #4] = 0xE5D10004
8748        let encoded = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8749        assert_eq!(encoded, 0xE5D10004, "Should encode LDRB R0, [R1, #4]");
8750    }
8751
8752    #[test]
8753    fn test_encode_strb_arm32() {
8754        let encoder = ArmEncoder::new_arm32();
8755        let op = ArmOp::Strb {
8756            rd: Reg::R0,
8757            addr: MemAddr::imm(Reg::R1, 0),
8758        };
8759        let code = encoder.encode(&op).unwrap();
8760        assert_eq!(code.len(), 4, "ARM32 STRB should be 4 bytes");
8761        // STRB R0, [R1, #0] = 0xE5C10000
8762        let encoded = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8763        assert_eq!(encoded, 0xE5C10000, "Should encode STRB R0, [R1, #0]");
8764    }
8765
8766    #[test]
8767    fn test_encode_ldrh_arm32() {
8768        let encoder = ArmEncoder::new_arm32();
8769        let op = ArmOp::Ldrh {
8770            rd: Reg::R0,
8771            addr: MemAddr::imm(Reg::R1, 2),
8772        };
8773        let code = encoder.encode(&op).unwrap();
8774        assert_eq!(code.len(), 4, "ARM32 LDRH should be 4 bytes");
8775    }
8776
8777    #[test]
8778    fn test_encode_strh_arm32() {
8779        let encoder = ArmEncoder::new_arm32();
8780        let op = ArmOp::Strh {
8781            rd: Reg::R0,
8782            addr: MemAddr::imm(Reg::R1, 0),
8783        };
8784        let code = encoder.encode(&op).unwrap();
8785        assert_eq!(code.len(), 4, "ARM32 STRH should be 4 bytes");
8786    }
8787
8788    #[test]
8789    fn test_encode_ldrsb_arm32() {
8790        let encoder = ArmEncoder::new_arm32();
8791        let op = ArmOp::Ldrsb {
8792            rd: Reg::R0,
8793            addr: MemAddr::imm(Reg::R1, 0),
8794        };
8795        let code = encoder.encode(&op).unwrap();
8796        assert_eq!(code.len(), 4, "ARM32 LDRSB should be 4 bytes");
8797    }
8798
8799    #[test]
8800    fn test_encode_ldrsh_arm32() {
8801        let encoder = ArmEncoder::new_arm32();
8802        let op = ArmOp::Ldrsh {
8803            rd: Reg::R0,
8804            addr: MemAddr::imm(Reg::R1, 0),
8805        };
8806        let code = encoder.encode(&op).unwrap();
8807        assert_eq!(code.len(), 4, "ARM32 LDRSH should be 4 bytes");
8808    }
8809
8810    #[test]
8811    fn test_encode_ldrb_thumb2_16bit() {
8812        let encoder = ArmEncoder::new_thumb2();
8813        let op = ArmOp::Ldrb {
8814            rd: Reg::R0,
8815            addr: MemAddr::imm(Reg::R1, 4),
8816        };
8817        let code = encoder.encode(&op).unwrap();
8818        // Low registers + small offset -> 16-bit encoding
8819        assert_eq!(
8820            code.len(),
8821            2,
8822            "Thumb-2 LDRB with small offset should be 16-bit"
8823        );
8824    }
8825
8826    #[test]
8827    fn test_encode_ldrb_thumb2_32bit() {
8828        let encoder = ArmEncoder::new_thumb2();
8829        let op = ArmOp::Ldrb {
8830            rd: Reg::R0,
8831            addr: MemAddr::imm(Reg::R1, 100), // offset > 31 needs 32-bit
8832        };
8833        let code = encoder.encode(&op).unwrap();
8834        assert_eq!(
8835            code.len(),
8836            4,
8837            "Thumb-2 LDRB with large offset should be 32-bit"
8838        );
8839    }
8840
8841    #[test]
8842    fn test_encode_strb_thumb2_16bit() {
8843        let encoder = ArmEncoder::new_thumb2();
8844        let op = ArmOp::Strb {
8845            rd: Reg::R0,
8846            addr: MemAddr::imm(Reg::R1, 10),
8847        };
8848        let code = encoder.encode(&op).unwrap();
8849        assert_eq!(
8850            code.len(),
8851            2,
8852            "Thumb-2 STRB with small offset should be 16-bit"
8853        );
8854    }
8855
8856    #[test]
8857    fn test_encode_ldrh_thumb2_16bit() {
8858        let encoder = ArmEncoder::new_thumb2();
8859        let op = ArmOp::Ldrh {
8860            rd: Reg::R0,
8861            addr: MemAddr::imm(Reg::R1, 4), // offset aligned to 2, <= 62
8862        };
8863        let code = encoder.encode(&op).unwrap();
8864        assert_eq!(
8865            code.len(),
8866            2,
8867            "Thumb-2 LDRH with small aligned offset should be 16-bit"
8868        );
8869    }
8870
8871    #[test]
8872    fn test_encode_strh_thumb2_16bit() {
8873        let encoder = ArmEncoder::new_thumb2();
8874        let op = ArmOp::Strh {
8875            rd: Reg::R0,
8876            addr: MemAddr::imm(Reg::R1, 4),
8877        };
8878        let code = encoder.encode(&op).unwrap();
8879        assert_eq!(
8880            code.len(),
8881            2,
8882            "Thumb-2 STRH with small aligned offset should be 16-bit"
8883        );
8884    }
8885
8886    #[test]
8887    fn test_encode_ldrsb_thumb2() {
8888        let encoder = ArmEncoder::new_thumb2();
8889        let op = ArmOp::Ldrsb {
8890            rd: Reg::R0,
8891            addr: MemAddr::imm(Reg::R1, 0),
8892        };
8893        let code = encoder.encode(&op).unwrap();
8894        // LDRSB has no 16-bit immediate form, always 32-bit
8895        assert_eq!(code.len(), 4, "Thumb-2 LDRSB should be 32-bit");
8896    }
8897
8898    #[test]
8899    fn test_encode_ldrsh_thumb2() {
8900        let encoder = ArmEncoder::new_thumb2();
8901        let op = ArmOp::Ldrsh {
8902            rd: Reg::R0,
8903            addr: MemAddr::imm(Reg::R1, 0),
8904        };
8905        let code = encoder.encode(&op).unwrap();
8906        assert_eq!(code.len(), 4, "Thumb-2 LDRSH should be 32-bit");
8907    }
8908
8909    #[test]
8910    fn test_encode_memory_size_thumb2() {
8911        let encoder = ArmEncoder::new_thumb2();
8912        let op = ArmOp::MemorySize { rd: Reg::R0 };
8913        let code = encoder.encode(&op).unwrap();
8914        // R0 and R10 are not both low registers, so this needs careful handling
8915        assert!(!code.is_empty(), "MemorySize should produce code");
8916    }
8917
8918    #[test]
8919    fn test_encode_memory_grow_thumb2() {
8920        let encoder = ArmEncoder::new_thumb2();
8921        let op = ArmOp::MemoryGrow {
8922            rd: Reg::R0,
8923            rn: Reg::R0,
8924        };
8925        let code = encoder.encode(&op).unwrap();
8926        assert_eq!(code.len(), 4, "MemoryGrow (MVN) should be 32-bit Thumb-2");
8927    }
8928
8929    #[test]
8930    fn test_encode_subword_reg_offset_thumb2() {
8931        let encoder = ArmEncoder::new_thumb2();
8932
8933        // LDRB with register offset
8934        let op = ArmOp::Ldrb {
8935            rd: Reg::R0,
8936            addr: MemAddr::reg(Reg::R1, Reg::R2),
8937        };
8938        let code = encoder.encode(&op).unwrap();
8939        assert_eq!(
8940            code.len(),
8941            4,
8942            "Thumb-2 LDRB with reg offset should be 32-bit"
8943        );
8944
8945        // STRB with register offset
8946        let op = ArmOp::Strb {
8947            rd: Reg::R0,
8948            addr: MemAddr::reg(Reg::R1, Reg::R2),
8949        };
8950        let code = encoder.encode(&op).unwrap();
8951        assert_eq!(
8952            code.len(),
8953            4,
8954            "Thumb-2 STRB with reg offset should be 32-bit"
8955        );
8956
8957        // LDRH with register offset
8958        let op = ArmOp::Ldrh {
8959            rd: Reg::R0,
8960            addr: MemAddr::reg(Reg::R1, Reg::R2),
8961        };
8962        let code = encoder.encode(&op).unwrap();
8963        assert_eq!(
8964            code.len(),
8965            4,
8966            "Thumb-2 LDRH with reg offset should be 32-bit"
8967        );
8968
8969        // STRH with register offset
8970        let op = ArmOp::Strh {
8971            rd: Reg::R0,
8972            addr: MemAddr::reg(Reg::R1, Reg::R2),
8973        };
8974        let code = encoder.encode(&op).unwrap();
8975        assert_eq!(
8976            code.len(),
8977            4,
8978            "Thumb-2 STRH with reg offset should be 32-bit"
8979        );
8980    }
8981
8982    #[test]
8983    fn test_encode_subword_reg_imm_offset_thumb2() {
8984        let encoder = ArmEncoder::new_thumb2();
8985
8986        // LDRB with both register and immediate offset
8987        let op = ArmOp::Ldrb {
8988            rd: Reg::R0,
8989            addr: MemAddr::reg_imm(Reg::R1, Reg::R2, 4),
8990        };
8991        let code = encoder.encode(&op).unwrap();
8992        // ADD R12, R2, #4 (4 bytes) + LDRB R0, [R1, R12] (4 bytes) = 8 bytes
8993        assert_eq!(
8994            code.len(),
8995            8,
8996            "Thumb-2 LDRB with reg+imm offset should be 8 bytes"
8997        );
8998    }
8999
9000    // ========================================================================
9001    // Helium MVE encoding tests
9002    // ========================================================================
9003
9004    #[test]
9005    fn test_encode_mve_addi32_thumb2() {
9006        let encoder = ArmEncoder::new_thumb2();
9007        let op = ArmOp::MveAddI {
9008            qd: QReg::Q0,
9009            qn: QReg::Q1,
9010            qm: QReg::Q2,
9011            size: MveSize::S32,
9012        };
9013        let code = encoder.encode(&op).unwrap();
9014        assert_eq!(
9015            code.len(),
9016            4,
9017            "MVE VADD.I32 should be 4 bytes (Thumb-2 32-bit)"
9018        );
9019    }
9020
9021    #[test]
9022    fn test_encode_mve_subi16_thumb2() {
9023        let encoder = ArmEncoder::new_thumb2();
9024        let op = ArmOp::MveSubI {
9025            qd: QReg::Q0,
9026            qn: QReg::Q1,
9027            qm: QReg::Q2,
9028            size: MveSize::S16,
9029        };
9030        let code = encoder.encode(&op).unwrap();
9031        assert_eq!(code.len(), 4, "MVE VSUB.I16 should be 4 bytes");
9032    }
9033
9034    #[test]
9035    fn test_encode_mve_muli8_thumb2() {
9036        let encoder = ArmEncoder::new_thumb2();
9037        let op = ArmOp::MveMulI {
9038            qd: QReg::Q0,
9039            qn: QReg::Q1,
9040            qm: QReg::Q2,
9041            size: MveSize::S8,
9042        };
9043        let code = encoder.encode(&op).unwrap();
9044        assert_eq!(code.len(), 4, "MVE VMUL.I8 should be 4 bytes");
9045    }
9046
9047    #[test]
9048    fn test_encode_mve_bitwise_thumb2() {
9049        let encoder = ArmEncoder::new_thumb2();
9050
9051        let ops = vec![
9052            ArmOp::MveAnd {
9053                qd: QReg::Q0,
9054                qn: QReg::Q1,
9055                qm: QReg::Q2,
9056            },
9057            ArmOp::MveOrr {
9058                qd: QReg::Q0,
9059                qn: QReg::Q1,
9060                qm: QReg::Q2,
9061            },
9062            ArmOp::MveEor {
9063                qd: QReg::Q0,
9064                qn: QReg::Q1,
9065                qm: QReg::Q2,
9066            },
9067            ArmOp::MveBic {
9068                qd: QReg::Q0,
9069                qn: QReg::Q1,
9070                qm: QReg::Q2,
9071            },
9072        ];
9073        for op in ops {
9074            let code = encoder.encode(&op).unwrap();
9075            assert_eq!(code.len(), 4, "MVE bitwise op should be 4 bytes");
9076        }
9077    }
9078
9079    #[test]
9080    fn test_encode_mve_mvn_thumb2() {
9081        let encoder = ArmEncoder::new_thumb2();
9082        let op = ArmOp::MveMvn {
9083            qd: QReg::Q0,
9084            qm: QReg::Q1,
9085        };
9086        let code = encoder.encode(&op).unwrap();
9087        assert_eq!(code.len(), 4, "MVE VMVN should be 4 bytes");
9088    }
9089
9090    #[test]
9091    fn test_encode_mve_load_store_thumb2() {
9092        let encoder = ArmEncoder::new_thumb2();
9093
9094        let load = ArmOp::MveLoad {
9095            qd: QReg::Q0,
9096            addr: MemAddr::imm(Reg::R0, 16),
9097        };
9098        let code = encoder.encode(&load).unwrap();
9099        assert_eq!(code.len(), 4, "MVE VLDRW.32 should be 4 bytes");
9100
9101        let store = ArmOp::MveStore {
9102            qd: QReg::Q1,
9103            addr: MemAddr::imm(Reg::R1, 0),
9104        };
9105        let code = encoder.encode(&store).unwrap();
9106        assert_eq!(code.len(), 4, "MVE VSTRW.32 should be 4 bytes");
9107    }
9108
9109    #[test]
9110    fn test_encode_mve_const_thumb2() {
9111        let encoder = ArmEncoder::new_thumb2();
9112        let op = ArmOp::MveConst {
9113            qd: QReg::Q0,
9114            bytes: [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0],
9115        };
9116        let code = encoder.encode(&op).unwrap();
9117        // Should be 4 words of (MOVW R12 + VMOV Sn) = 4 * (4+4) = 32 bytes min
9118        // Some words with hi16=0 skip MOVT, so length varies
9119        assert!(
9120            code.len() >= 24,
9121            "MVE const should produce multiple instructions"
9122        );
9123    }
9124
9125    #[test]
9126    fn test_encode_mve_dup_thumb2() {
9127        let encoder = ArmEncoder::new_thumb2();
9128        let op = ArmOp::MveDup {
9129            qd: QReg::Q0,
9130            rn: Reg::R0,
9131            size: MveSize::S32,
9132        };
9133        let code = encoder.encode(&op).unwrap();
9134        assert_eq!(code.len(), 4, "MVE VDUP.32 should be 4 bytes");
9135    }
9136
9137    #[test]
9138    fn test_encode_mve_extract_lane_thumb2() {
9139        let encoder = ArmEncoder::new_thumb2();
9140        let op = ArmOp::MveExtractLane {
9141            rd: Reg::R0,
9142            qn: QReg::Q1,
9143            lane: 2,
9144            size: MveSize::S32,
9145        };
9146        let code = encoder.encode(&op).unwrap();
9147        assert_eq!(code.len(), 4, "MVE extract lane should be 4 bytes");
9148    }
9149
9150    #[test]
9151    fn test_encode_mve_insert_lane_thumb2() {
9152        let encoder = ArmEncoder::new_thumb2();
9153        let op = ArmOp::MveInsertLane {
9154            qd: QReg::Q0,
9155            rn: Reg::R1,
9156            lane: 3,
9157            size: MveSize::S32,
9158        };
9159        let code = encoder.encode(&op).unwrap();
9160        assert_eq!(code.len(), 4, "MVE insert lane should be 4 bytes");
9161    }
9162
9163    #[test]
9164    fn test_encode_mve_addf32_thumb2() {
9165        let encoder = ArmEncoder::new_thumb2();
9166        let op = ArmOp::MveAddF32 {
9167            qd: QReg::Q0,
9168            qn: QReg::Q1,
9169            qm: QReg::Q2,
9170        };
9171        let code = encoder.encode(&op).unwrap();
9172        assert_eq!(code.len(), 4, "MVE VADD.F32 should be 4 bytes");
9173    }
9174
9175    #[test]
9176    fn test_encode_mve_divf32_thumb2() {
9177        let encoder = ArmEncoder::new_thumb2();
9178        let op = ArmOp::MveDivF32 {
9179            qd: QReg::Q0,
9180            qn: QReg::Q1,
9181            qm: QReg::Q2,
9182        };
9183        let code = encoder.encode(&op).unwrap();
9184        // Lane-wise: 4 x VDIV.F32 = 4 x 4 = 16 bytes
9185        assert_eq!(
9186            code.len(),
9187            16,
9188            "MVE VDIV.F32 (lane-wise) should be 16 bytes"
9189        );
9190    }
9191
9192    #[test]
9193    fn test_encode_mve_sqrtf32_thumb2() {
9194        let encoder = ArmEncoder::new_thumb2();
9195        let op = ArmOp::MveSqrtF32 {
9196            qd: QReg::Q0,
9197            qm: QReg::Q1,
9198        };
9199        let code = encoder.encode(&op).unwrap();
9200        // Lane-wise: 4 x VSQRT.F32 = 4 x 4 = 16 bytes
9201        assert_eq!(
9202            code.len(),
9203            16,
9204            "MVE VSQRT.F32 (lane-wise) should be 16 bytes"
9205        );
9206    }
9207
9208    #[test]
9209    fn test_encode_mve_negf32_thumb2() {
9210        let encoder = ArmEncoder::new_thumb2();
9211        let op = ArmOp::MveNegF32 {
9212            qd: QReg::Q0,
9213            qm: QReg::Q1,
9214        };
9215        let code = encoder.encode(&op).unwrap();
9216        assert_eq!(code.len(), 4, "MVE VNEG.F32 should be 4 bytes");
9217    }
9218
9219    #[test]
9220    fn test_encode_mve_absf32_thumb2() {
9221        let encoder = ArmEncoder::new_thumb2();
9222        let op = ArmOp::MveAbsF32 {
9223            qd: QReg::Q0,
9224            qm: QReg::Q1,
9225        };
9226        let code = encoder.encode(&op).unwrap();
9227        assert_eq!(code.len(), 4, "MVE VABS.F32 should be 4 bytes");
9228    }
9229
9230    /// VCR-RA-001 / immediate-folding precondition: pins the Thumb-2 `AND`
9231    /// immediate encoding for the byte range and documents its bound.
9232    ///
9233    /// The `And { Operand2::Imm }` encoder packs the low 12 bits straight into
9234    /// the `i:imm3:imm8` field WITHOUT applying ThumbExpandImm (the modified-
9235    /// immediate expansion). For `imm <= 0xFF` (e.g. gale's int8 clamps
9236    /// `#0x7e` / `#0x7f`) that is correct — `i:imm3 = 0000` means "imm8
9237    /// zero-extended". So `and r2, r0, #0x7e` encodes to the canonical
9238    /// `00 f0 7e 02`. For `imm >= 0x100` the field would need a true
9239    /// ThumbExpandImm pattern (rotation / replication), which is NOT
9240    /// implemented here — so **immediate folding must gate on `imm <= 0xFF`**
9241    /// until the encoder is hardened to ThumbExpandImm/Ok-or-Err (the
9242    /// "encoder must be Ok-or-Err, never silently wrong" principle, #180/#185).
9243    /// This bound covers the measured `flat_flight` waste (#209).
9244    #[test]
9245    fn and_immediate_encodes_correctly_in_byte_range_documents_fold_bound() {
9246        let encoder = ArmEncoder::new_thumb2();
9247        let op = ArmOp::And {
9248            rd: Reg::R2,
9249            rn: Reg::R0,
9250            op2: Operand2::Imm(0x7e),
9251        };
9252        let code = encoder.encode(&op).unwrap();
9253        assert_eq!(
9254            code,
9255            vec![0x00, 0xf0, 0x7e, 0x02],
9256            "and r2, r0, #0x7e must encode to the canonical AND.W T1 (imm8=0x7e)"
9257        );
9258    }
9259
9260    /// #255: the shared ThumbExpandImm reverse-encoder underpinning the
9261    /// data-processing immediate fix. Encodable modified immediates round-trip to
9262    /// the expected `i:imm3:imm8` field; a genuinely non-modified value is `None`
9263    /// (caller must materialize into a register). Note `1000 = 0xFA ror 30` *is*
9264    /// representable (field 0xF7A) — the old encoder mis-encoded it (raw 0x3E8);
9265    /// this encodes it correctly.
9266    #[test]
9267    fn try_thumb_expand_imm_encodes_modified_immediates() {
9268        assert_eq!(try_thumb_expand_imm(0x7e), Some(0x07e)); // zero-extended byte
9269        assert_eq!(try_thumb_expand_imm(0xff), Some(0x0ff));
9270        assert_eq!(try_thumb_expand_imm(0x0001_0001), Some(0x101)); // 0x00XY00XY
9271        assert_eq!(try_thumb_expand_imm(0xff00_ff00), Some(0x2ff)); // 0xXY00XY00
9272        assert_eq!(try_thumb_expand_imm(0xffff_ffff), Some(0x3ff)); // 0xXYXYXYXY
9273        assert_eq!(try_thumb_expand_imm(0x100), Some(0xf80)); // 0x80 ror 31
9274        assert_eq!(try_thumb_expand_imm(0x8000_0000), Some(0x400)); // 0x80 ror 8
9275        assert_eq!(try_thumb_expand_imm(1000), Some(0xf7a)); // 0xFA ror 30
9276        // Genuinely unrepresentable (bits too far apart for an 8-bit window).
9277        assert_eq!(try_thumb_expand_imm(0x101), None);
9278        assert_eq!(try_thumb_expand_imm(0x12345), None);
9279    }
9280
9281    /// #255: CMP/ADDS/SUBS encode any valid modified immediate correctly, and
9282    /// ERROR (not silently mis-encode) on a genuinely unrepresentable one,
9283    /// forcing the selector to materialize into a register — closing the
9284    /// silent-miscompile class of #251/#253.
9285    #[test]
9286    fn cmp_adds_subs_immediate_error_on_non_modified_imm() {
9287        let encoder = ArmEncoder::new_thumb2();
9288        // cmp r0, #0xff → valid → Ok; cmp r0, #1000 → valid (0xFA ror 30) → Ok.
9289        assert!(encoder.encode_thumb32_cmp_imm(&Reg::R0, 0xff).is_ok());
9290        assert!(encoder.encode_thumb32_cmp_imm(&Reg::R0, 1000).is_ok());
9291        // cmp r0, #0x101 → NOT a modified immediate → Err (materialize-reg).
9292        assert!(
9293            encoder.encode_thumb32_cmp_imm(&Reg::R0, 0x101).is_err(),
9294            "cmp #0x101 must error, not compare the wrong constant"
9295        );
9296        assert!(
9297            encoder
9298                .encode_thumb32_adds(&Reg::R0, &Reg::R0, 0x101)
9299                .is_err()
9300        );
9301        assert!(
9302            encoder
9303                .encode_thumb32_subs(&Reg::R0, &Reg::R0, 0x101)
9304                .is_err()
9305        );
9306        // ...but a valid modified immediate still encodes.
9307        assert!(
9308            encoder
9309                .encode_thumb32_adds(&Reg::R0, &Reg::R0, 0x80)
9310                .is_ok()
9311        );
9312    }
9313
9314    /// #257: MLA (multiply-accumulate) encodes as MLS without the bit-4 op flag.
9315    /// `mla r2, r3, r4, r8` (rd=r2, rn=r3, rm=r4, ra=r8) → Thumb-2 `03 fb 04 82`.
9316    #[test]
9317    fn mla_thumb2_encodes_correctly() {
9318        let encoder = ArmEncoder::new_thumb2();
9319        let code = encoder
9320            .encode(&ArmOp::Mla {
9321                rd: Reg::R2,
9322                rn: Reg::R3,
9323                rm: Reg::R4,
9324                ra: Reg::R8,
9325            })
9326            .unwrap();
9327        // hw1 = 0xFB03, hw2 = (8<<12)|(2<<8)|4 = 0x8204
9328        assert_eq!(code, vec![0x03, 0xfb, 0x04, 0x82]);
9329    }
9330
9331    /// #259: LDR/STR (and sub-word) immediate-offset encoders truncated
9332    /// `offset & 0xFFF`, silently targeting the wrong address for offset >= 4096.
9333    /// They now error (the selector must use register-offset addressing) — the
9334    /// load/store sibling of the #253/#255 class. Offsets <= 4095 still encode.
9335    #[test]
9336    fn ldst_imm12_offset_errors_when_out_of_range() {
9337        let encoder = ArmEncoder::new_thumb2();
9338        // offset 0xFFF (4095): valid → Ok; ldr r0, [r1, #4095].
9339        assert!(
9340            encoder
9341                .encode_thumb32_ldr(&Reg::R0, &Reg::R1, 0xFFF)
9342                .is_ok()
9343        );
9344        // offset 0x1000 (4096): out of imm12 range → Err (not & 0xFFF → #0).
9345        assert!(
9346            encoder
9347                .encode_thumb32_ldr(&Reg::R0, &Reg::R1, 0x1000)
9348                .is_err(),
9349            "ldr offset 4096 must error, not wrap to 0"
9350        );
9351        assert!(
9352            encoder
9353                .encode_thumb32_str(&Reg::R0, &Reg::R1, 0x1000)
9354                .is_err()
9355        );
9356        assert!(
9357            encoder
9358                .encode_thumb32_ldrb_imm(&Reg::R0, &Reg::R1, 5000)
9359                .is_err()
9360        );
9361        assert!(
9362            encoder
9363                .encode_thumb32_strh_imm(&Reg::R0, &Reg::R1, 5000)
9364                .is_err()
9365        );
9366    }
9367
9368    /// Latent miscompile fix: ADD/SUB with a >0xFF immediate (e.g.
9369    /// `add sp, sp, #frame` for a >=256-byte frame) used ADD.W (T3), whose
9370    /// `i:imm3:imm8` is a ThumbExpandImm modified immediate — so `#256` silently
9371    /// encoded as `#0` (stack corruption). Use ADDW/SUBW (T4), a PLAIN 12-bit
9372    /// immediate, for 0x100..=0xFFF; keep T3 for <=0xFF (bit-identical); error
9373    /// beyond 4095.
9374    #[test]
9375    fn add_sub_large_immediate_use_addw_subw_not_misencoded() {
9376        let encoder = ArmEncoder::new_thumb2();
9377        // add sp, sp, #256  →  ADDW (T4) SP, SP, #256  =  0d f2 00 1d
9378        assert_eq!(
9379            encoder
9380                .encode(&ArmOp::Add {
9381                    rd: Reg::SP,
9382                    rn: Reg::SP,
9383                    op2: Operand2::Imm(256),
9384                })
9385                .unwrap(),
9386            vec![0x0d, 0xf2, 0x00, 0x1d],
9387            "add sp,sp,#256 must be ADDW (plain imm12), not a mis-encoded ADD.W"
9388        );
9389        // sub sp, sp, #256  →  SUBW (T4) SP, SP, #256  =  ad f2 00 1d
9390        assert_eq!(
9391            encoder
9392                .encode(&ArmOp::Sub {
9393                    rd: Reg::SP,
9394                    rn: Reg::SP,
9395                    op2: Operand2::Imm(256),
9396                })
9397                .unwrap(),
9398            vec![0xad, 0xf2, 0x00, 0x1d],
9399        );
9400        // > 4095 has no single-instruction encoding → error, not silent wrong.
9401        assert!(
9402            encoder
9403                .encode(&ArmOp::Add {
9404                    rd: Reg::SP,
9405                    rn: Reg::SP,
9406                    op2: Operand2::Imm(5000),
9407                })
9408                .is_err(),
9409            "add #5000 must error (no single ADDW), not mis-encode"
9410        );
9411    }
9412
9413    /// Closes the data-proc immediate class: AND and CMN now go through
9414    /// `try_thumb_expand_imm` like ORR/EOR/CMP — correct for any modified
9415    /// immediate, `Err` (not raw-pack / NOP) on an un-encodable one. The byte
9416    /// range stays bit-identical (`and r2,r0,#0x7e` is unchanged).
9417    #[test]
9418    fn and_cmn_immediate_thumb_expand_else_error() {
9419        let encoder = ArmEncoder::new_thumb2();
9420        // byte range unchanged (bit-identical with the pre-retrofit encoding)
9421        assert_eq!(
9422            encoder
9423                .encode(&ArmOp::And {
9424                    rd: Reg::R2,
9425                    rn: Reg::R0,
9426                    op2: Operand2::Imm(0x7e),
9427                })
9428                .unwrap(),
9429            vec![0x00, 0xf0, 0x7e, 0x02],
9430        );
9431        // a valid replicated modified immediate now encodes (was silently wrong)
9432        assert!(
9433            encoder
9434                .encode(&ArmOp::And {
9435                    rd: Reg::R2,
9436                    rn: Reg::R0,
9437                    op2: Operand2::Imm(0xff00ff00u32 as i32),
9438                })
9439                .is_ok()
9440        );
9441        // a genuinely un-encodable immediate errors (AND was raw-pack; CMN NOP)
9442        assert!(
9443            encoder
9444                .encode(&ArmOp::And {
9445                    rd: Reg::R2,
9446                    rn: Reg::R0,
9447                    op2: Operand2::Imm(0x101),
9448                })
9449                .is_err()
9450        );
9451        assert!(
9452            encoder
9453                .encode(&ArmOp::Cmn {
9454                    rn: Reg::R0,
9455                    op2: Operand2::Imm(0x101),
9456                })
9457                .is_err(),
9458            "CMN #0x101 must error, not emit a NOP"
9459        );
9460    }
9461
9462    /// VCR-RA-001: ORR/EOR with a small immediate must encode the real
9463    /// instruction (not a silent `0xBF00` NOP). Pins the byte range and the
9464    /// Ok-or-Err bound that makes future Or/Eor immediate folding safe.
9465    #[test]
9466    fn orr_eor_immediate_encode_in_byte_range_else_error() {
9467        let encoder = ArmEncoder::new_thumb2();
9468        // orr r2, r0, #0x7e  →  ORR.W T1, imm8=0x7e
9469        assert_eq!(
9470            encoder
9471                .encode(&ArmOp::Orr {
9472                    rd: Reg::R2,
9473                    rn: Reg::R0,
9474                    op2: Operand2::Imm(0x7e),
9475                })
9476                .unwrap(),
9477            vec![0x40, 0xf0, 0x7e, 0x02],
9478        );
9479        // eor r2, r0, #0x7e  →  EOR.W T1, imm8=0x7e
9480        assert_eq!(
9481            encoder
9482                .encode(&ArmOp::Eor {
9483                    rd: Reg::R2,
9484                    rn: Reg::R0,
9485                    op2: Operand2::Imm(0x7e),
9486                })
9487                .unwrap(),
9488            vec![0x80, 0xf0, 0x7e, 0x02],
9489        );
9490        // Out-of-range immediates error rather than silently mis-encode / NOP.
9491        assert!(
9492            encoder
9493                .encode(&ArmOp::Orr {
9494                    rd: Reg::R2,
9495                    rn: Reg::R0,
9496                    op2: Operand2::Imm(0x140),
9497                })
9498                .is_err(),
9499            "ORR #0x140 must error, not emit a NOP"
9500        );
9501    }
9502
9503    #[test]
9504    fn test_encode_mve_different_qregs() {
9505        let encoder = ArmEncoder::new_thumb2();
9506
9507        // Test that different Q-register numbers produce different encodings
9508        let op1 = ArmOp::MveAddI {
9509            qd: QReg::Q0,
9510            qn: QReg::Q0,
9511            qm: QReg::Q0,
9512            size: MveSize::S32,
9513        };
9514        let op2 = ArmOp::MveAddI {
9515            qd: QReg::Q3,
9516            qn: QReg::Q5,
9517            qm: QReg::Q7,
9518            size: MveSize::S32,
9519        };
9520        let code1 = encoder.encode(&op1).unwrap();
9521        let code2 = encoder.encode(&op2).unwrap();
9522        assert_ne!(
9523            code1, code2,
9524            "Different Q-registers should produce different encodings"
9525        );
9526    }
9527
9528    #[test]
9529    fn test_encode_mve_arm32_nop() {
9530        // MVE instructions on ARM32 encoder should produce NOP (only Thumb-2 supported)
9531        let encoder = ArmEncoder::new_arm32();
9532        let op = ArmOp::MveAddI {
9533            qd: QReg::Q0,
9534            qn: QReg::Q1,
9535            qm: QReg::Q2,
9536            size: MveSize::S32,
9537        };
9538        let code = encoder.encode(&op).unwrap();
9539        assert_eq!(code.len(), 4, "ARM32 MVE should be 4 bytes (NOP)");
9540        // NOP in ARM32 is 0xE1A00000 (MOV R0, R0)
9541        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
9542        assert_eq!(instr, 0xE1A00000, "ARM32 MVE should encode as NOP");
9543    }
9544}