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::And { rd, rn, op2 } => {
247                let rd_bits = reg_to_bits(rd);
248                let rn_bits = reg_to_bits(rn);
249                let (op2_bits, i_flag) = encode_operand2(op2);
250
251                // AND encoding: opcode=0000
252                0xE0000000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
253            }
254
255            ArmOp::Orr { rd, rn, op2 } => {
256                let rd_bits = reg_to_bits(rd);
257                let rn_bits = reg_to_bits(rn);
258                let (op2_bits, i_flag) = encode_operand2(op2);
259
260                // ORR encoding: opcode=1100
261                0xE1800000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
262            }
263
264            ArmOp::Eor { rd, rn, op2 } => {
265                let rd_bits = reg_to_bits(rd);
266                let rn_bits = reg_to_bits(rn);
267                let (op2_bits, i_flag) = encode_operand2(op2);
268
269                // EOR encoding: opcode=0001
270                0xE0200000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
271            }
272
273            // Shift instructions
274            ArmOp::Lsl { rd, rn, shift } => {
275                let rd_bits = reg_to_bits(rd);
276                let rn_bits = reg_to_bits(rn);
277                let shift_bits = *shift & 0x1F;
278
279                // LSL encoding: MOV with shift
280                0xE1A00000 | (rd_bits << 12) | (shift_bits << 7) | rn_bits
281            }
282
283            ArmOp::Lsr { rd, rn, shift } => {
284                let rd_bits = reg_to_bits(rd);
285                let rn_bits = reg_to_bits(rn);
286                let shift_bits = *shift & 0x1F;
287
288                // LSR encoding
289                0xE1A00020 | (rd_bits << 12) | (shift_bits << 7) | rn_bits
290            }
291
292            ArmOp::Asr { rd, rn, shift } => {
293                let rd_bits = reg_to_bits(rd);
294                let rn_bits = reg_to_bits(rn);
295                let shift_bits = *shift & 0x1F;
296
297                // ASR encoding
298                0xE1A00040 | (rd_bits << 12) | (shift_bits << 7) | rn_bits
299            }
300
301            ArmOp::Ror { rd, rn, shift } => {
302                let rd_bits = reg_to_bits(rd);
303                let rn_bits = reg_to_bits(rn);
304                let shift_bits = *shift & 0x1F;
305
306                // ROR encoding: MOV with ROR shift
307                0xE1A00060 | (rd_bits << 12) | (shift_bits << 7) | rn_bits
308            }
309
310            // Register-based shifts (ARM32)
311            // LSL Rd, Rn, Rm: cond 0001101S 0000 Rd Rs 0001 Rn
312            ArmOp::LslReg { rd, rn, rm } => {
313                let rd_bits = reg_to_bits(rd);
314                let rn_bits = reg_to_bits(rn);
315                let rm_bits = reg_to_bits(rm);
316                0xE1A00010 | (rd_bits << 12) | (rm_bits << 8) | rn_bits
317            }
318            ArmOp::LsrReg { rd, rn, rm } => {
319                let rd_bits = reg_to_bits(rd);
320                let rn_bits = reg_to_bits(rn);
321                let rm_bits = reg_to_bits(rm);
322                0xE1A00030 | (rd_bits << 12) | (rm_bits << 8) | rn_bits
323            }
324            ArmOp::AsrReg { rd, rn, rm } => {
325                let rd_bits = reg_to_bits(rd);
326                let rn_bits = reg_to_bits(rn);
327                let rm_bits = reg_to_bits(rm);
328                0xE1A00050 | (rd_bits << 12) | (rm_bits << 8) | rn_bits
329            }
330            ArmOp::RorReg { rd, rn, rm } => {
331                let rd_bits = reg_to_bits(rd);
332                let rn_bits = reg_to_bits(rn);
333                let rm_bits = reg_to_bits(rm);
334                0xE1A00070 | (rd_bits << 12) | (rm_bits << 8) | rn_bits
335            }
336
337            // RSB (Reverse Subtract): Rd = imm - Rn
338            ArmOp::Rsb { rd, rn, imm } => {
339                let rd_bits = reg_to_bits(rd);
340                let rn_bits = reg_to_bits(rn);
341                // RSB encoding: cond(4) | 00 1 0011 S | Rn(4) | Rd(4) | imm12
342                // Opcode for RSB = 0011, I=1 (immediate), S=0
343                0xE2600000 | (rn_bits << 16) | (rd_bits << 12) | (*imm & 0xFF)
344            }
345
346            // Bit manipulation instructions
347            ArmOp::Clz { rd, rm } => {
348                let rd_bits = reg_to_bits(rd);
349                let rm_bits = reg_to_bits(rm);
350
351                // CLZ encoding: cond(4) | 00010110 | 1111 | Rd(4) | 1111 | 0001 | Rm(4)
352                // ARMv5T and above
353                0xE16F0F10 | (rd_bits << 12) | rm_bits
354            }
355
356            ArmOp::Rbit { rd, rm } => {
357                let rd_bits = reg_to_bits(rd);
358                let rm_bits = reg_to_bits(rm);
359
360                // RBIT encoding: cond(4) | 01101111 | 1111 | Rd(4) | 1111 | 0011 | Rm(4)
361                // ARMv6T2 and above
362                0xE6FF0F30 | (rd_bits << 12) | rm_bits
363            }
364
365            ArmOp::Sxtb { rd, rm } => {
366                let rd_bits = reg_to_bits(rd);
367                let rm_bits = reg_to_bits(rm);
368
369                // SXTB encoding: cond(4) | 01101010 | 1111 | Rd(4) | rotate(2) | 00 | 0111 | Rm(4)
370                // ARMv6 and above. rotate=00 for no rotation
371                0xE6AF0070 | (rd_bits << 12) | rm_bits
372            }
373
374            ArmOp::Sxth { rd, rm } => {
375                let rd_bits = reg_to_bits(rd);
376                let rm_bits = reg_to_bits(rm);
377
378                // SXTH encoding: cond(4) | 01101011 | 1111 | Rd(4) | rotate(2) | 00 | 0111 | Rm(4)
379                // ARMv6 and above. rotate=00 for no rotation
380                0xE6BF0070 | (rd_bits << 12) | rm_bits
381            }
382
383            // Move instructions
384            ArmOp::Mov { rd, op2 } => {
385                let rd_bits = reg_to_bits(rd);
386                let (op2_bits, i_flag) = encode_operand2(op2);
387
388                // MOV encoding: opcode=1101
389                0xE1A00000 | (i_flag << 25) | (rd_bits << 12) | op2_bits
390            }
391
392            ArmOp::Mvn { rd, op2 } => {
393                let rd_bits = reg_to_bits(rd);
394                let (op2_bits, i_flag) = encode_operand2(op2);
395
396                // MVN encoding: opcode=1111
397                0xE1E00000 | (i_flag << 25) | (rd_bits << 12) | op2_bits
398            }
399
400            // MOVW - Move Wide (ARM32)
401            // Encoding: cond(4) | 0011 0000 | imm4(4) | Rd(4) | imm12(12)
402            ArmOp::Movw { rd, imm16 } => {
403                let rd_bits = reg_to_bits(rd);
404                let imm4 = ((*imm16 as u32) >> 12) & 0xF;
405                let imm12 = (*imm16 as u32) & 0xFFF;
406                0xE3000000 | (imm4 << 16) | (rd_bits << 12) | imm12
407            }
408
409            // MOVT - Move Top (ARM32)
410            // Encoding: cond(4) | 0011 0100 | imm4(4) | Rd(4) | imm12(12)
411            ArmOp::Movt { rd, imm16 } => {
412                let rd_bits = reg_to_bits(rd);
413                let imm4 = ((*imm16 as u32) >> 12) & 0xF;
414                let imm12 = (*imm16 as u32) & 0xFFF;
415                0xE3400000 | (imm4 << 16) | (rd_bits << 12) | imm12
416            }
417
418            // #237: symbol-relative MOVW/MOVT (ARM mode) — addend in place, the
419            // backend records the MOVW_ABS/MOVT_ABS relocation against `symbol`.
420            ArmOp::MovwSym { rd, addend, .. } => {
421                let rd_bits = reg_to_bits(rd);
422                let v = (*addend as u32) & 0xffff;
423                0xE3000000 | (((v >> 12) & 0xF) << 16) | (rd_bits << 12) | (v & 0xFFF)
424            }
425            ArmOp::MovtSym { rd, addend, .. } => {
426                let rd_bits = reg_to_bits(rd);
427                let v = ((*addend as u32) >> 16) & 0xffff;
428                0xE3400000 | (((v >> 12) & 0xF) << 16) | (rd_bits << 12) | (v & 0xFFF)
429            }
430
431            // Compare
432            ArmOp::Cmp { rn, op2 } => {
433                let rn_bits = reg_to_bits(rn);
434                let (op2_bits, i_flag) = encode_operand2(op2);
435
436                // CMP encoding: opcode=1010, S=1
437                0xE1500000 | (i_flag << 25) | (rn_bits << 16) | op2_bits
438            }
439
440            // Compare Negative (CMN) - computes Rn + op2 and sets flags
441            ArmOp::Cmn { rn, op2 } => {
442                let rn_bits = reg_to_bits(rn);
443                let (op2_bits, i_flag) = encode_operand2(op2);
444
445                // CMN encoding: opcode=1011, S=1
446                0xE1700000 | (i_flag << 25) | (rn_bits << 16) | op2_bits
447            }
448
449            // Load/Store
450            ArmOp::Ldr { rd, addr } => {
451                let rd_bits = reg_to_bits(rd);
452                let (base_bits, offset_bits) = encode_mem_addr(addr);
453
454                // LDR encoding: cond(4) | 01 | I(1) | P(1) | U(1) | B(1) | W(1) | L(1) | Rn(4) | Rd(4) | offset(12)
455                // P=1 (pre-indexed), U=1 (add offset), L=1 (load)
456                0xE5900000 | (base_bits << 16) | (rd_bits << 12) | offset_bits
457            }
458
459            ArmOp::Str { rd, addr } => {
460                let rd_bits = reg_to_bits(rd);
461                let (base_bits, offset_bits) = encode_mem_addr(addr);
462
463                // STR encoding: L=0 (store)
464                0xE5800000 | (base_bits << 16) | (rd_bits << 12) | offset_bits
465            }
466
467            // Sub-word loads (ARM32 encoding)
468            ArmOp::Ldrb { rd, addr } => {
469                let rd_bits = reg_to_bits(rd);
470                let (base_bits, offset_bits) = encode_mem_addr(addr);
471                // LDRB: LDR with B=1 (byte): cond|01|I|P|U|1|W|L|Rn|Rd|offset
472                0xE5D00000 | (base_bits << 16) | (rd_bits << 12) | offset_bits
473            }
474
475            ArmOp::Ldrsb { rd, addr } => {
476                let rd_bits = reg_to_bits(rd);
477                let (base_bits, offset_bits) = encode_mem_addr(addr);
478                // LDRSB (misc load): cond|000|P|U|1|W|1|Rn|Rd|imm4H|1101|imm4L
479                // Simplified with immediate offset
480                let offset_val = offset_bits & 0xFF;
481                let imm4h = (offset_val >> 4) & 0xF;
482                let imm4l = offset_val & 0xF;
483                0xE1D000D0 | (base_bits << 16) | (rd_bits << 12) | (imm4h << 8) | imm4l
484            }
485
486            ArmOp::Ldrh { rd, addr } => {
487                let rd_bits = reg_to_bits(rd);
488                let (base_bits, offset_bits) = encode_mem_addr(addr);
489                // LDRH (misc load): cond|000|P|U|1|W|1|Rn|Rd|imm4H|1011|imm4L
490                let offset_val = offset_bits & 0xFF;
491                let imm4h = (offset_val >> 4) & 0xF;
492                let imm4l = offset_val & 0xF;
493                0xE1D000B0 | (base_bits << 16) | (rd_bits << 12) | (imm4h << 8) | imm4l
494            }
495
496            ArmOp::Ldrsh { rd, addr } => {
497                let rd_bits = reg_to_bits(rd);
498                let (base_bits, offset_bits) = encode_mem_addr(addr);
499                // LDRSH (misc load): cond|000|P|U|1|W|1|Rn|Rd|imm4H|1111|imm4L
500                let offset_val = offset_bits & 0xFF;
501                let imm4h = (offset_val >> 4) & 0xF;
502                let imm4l = offset_val & 0xF;
503                0xE1D000F0 | (base_bits << 16) | (rd_bits << 12) | (imm4h << 8) | imm4l
504            }
505
506            // Sub-word stores (ARM32 encoding)
507            ArmOp::Strb { rd, addr } => {
508                let rd_bits = reg_to_bits(rd);
509                let (base_bits, offset_bits) = encode_mem_addr(addr);
510                // STRB: STR with B=1 (byte): cond|01|I|P|U|1|W|0|Rn|Rd|offset
511                0xE5C00000 | (base_bits << 16) | (rd_bits << 12) | offset_bits
512            }
513
514            ArmOp::Strh { rd, addr } => {
515                let rd_bits = reg_to_bits(rd);
516                let (base_bits, offset_bits) = encode_mem_addr(addr);
517                // STRH (misc store): cond|000|P|U|1|W|0|Rn|Rd|imm4H|1011|imm4L
518                let offset_val = offset_bits & 0xFF;
519                let imm4h = (offset_val >> 4) & 0xF;
520                let imm4l = offset_val & 0xF;
521                0xE1C000B0 | (base_bits << 16) | (rd_bits << 12) | (imm4h << 8) | imm4l
522            }
523
524            // Memory management (ARM32 encoding)
525            ArmOp::MemorySize { rd } => {
526                let rd_bits = reg_to_bits(rd);
527                // MOV rd, R10, LSR #16  (memory size in bytes / 65536 = pages)
528                // cond|000|1101|S|0000|Rd|shift5|type|0|Rm
529                // LSR #16: shift5=10000, type=01
530                0xE1A00820 | (rd_bits << 12) | 0x0A // Rm=R10, shift=16, LSR
531            }
532
533            ArmOp::MemoryGrow { rd, .. } => {
534                let rd_bits = reg_to_bits(rd);
535                // On embedded, always fail: MOV rd, #-1
536                0xE3E00000 | (rd_bits << 12) // MVN rd, #0 = MOV rd, #-1
537            }
538
539            // Label pseudo-instruction: emits no machine code
540            ArmOp::Label { .. } => {
541                return Ok(Vec::new());
542            }
543
544            // Branch instructions
545            ArmOp::B { label: _ } => {
546                // B encoding: cond(4) | 1010 | offset(24)
547                // Simplified: branch to offset 0 (will be patched by linker/resolver)
548                0xEA000000
549            }
550
551            // Conditional branch to label (generic)
552            ArmOp::Bcc { cond, label: _ } => {
553                use synth_synthesis::Condition;
554                let cond_bits: u32 = match cond {
555                    Condition::EQ => 0x0,
556                    Condition::NE => 0x1,
557                    Condition::HS => 0x2,
558                    Condition::LO => 0x3,
559                    Condition::HI => 0x8,
560                    Condition::LS => 0x9,
561                    Condition::GE => 0xA,
562                    Condition::LT => 0xB,
563                    Condition::GT => 0xC,
564                    Condition::LE => 0xD,
565                };
566                // B<cond> with offset 0 (will be patched)
567                (cond_bits << 28) | 0x0A000000
568            }
569
570            // BHS (Branch if Higher or Same) - used for bounds checking
571            ArmOp::Bhs { label: _ } => {
572                // BHS encoding: cond(2=HS) | 1010 | offset(24)
573                0x2A000000 // BHS with offset 0
574            }
575
576            // BLO (Branch if Lower) - complementary to BHS
577            ArmOp::Blo { label: _ } => {
578                // BLO encoding: cond(3=LO) | 1010 | offset(24)
579                0x3A000000 // BLO with offset 0
580            }
581
582            // Branch with numeric offset (in instructions)
583            // ARM32 B instruction: offset is in instructions, stored as words
584            // The offset is relative to PC+8 (due to ARM pipeline)
585            ArmOp::BOffset { offset } => {
586                // B encoding: cond(4) | 1010 | offset(24)
587                // Offset is signed, in words (4-byte units)
588                // ARM adds PC+8 to the offset, so we need to adjust:
589                // target = PC + 8 + (offset * 4)
590                // For backward branch of N instructions: offset = -(N + 2)
591                // wrapping_sub keeps the encoder total under fuzzing (#186): an
592                // extreme i32::MIN offset would otherwise overflow-panic; for any
593                // real branch offset this is identical to `- 2`.
594                let adjusted_offset = offset.wrapping_sub(2); // Account for PC+8
595                let offset_bits = (adjusted_offset as u32) & 0x00FFFFFF;
596                0xEA000000 | offset_bits
597            }
598
599            // Conditional branch with numeric offset
600            ArmOp::BCondOffset { cond, offset } => {
601                use synth_synthesis::Condition;
602                let cond_bits: u32 = match cond {
603                    Condition::EQ => 0x0,
604                    Condition::NE => 0x1,
605                    Condition::HS => 0x2,
606                    Condition::LO => 0x3,
607                    Condition::HI => 0x8,
608                    Condition::LS => 0x9,
609                    Condition::GE => 0xA,
610                    Condition::LT => 0xB,
611                    Condition::GT => 0xC,
612                    Condition::LE => 0xD,
613                };
614                // B<cond> encoding: cond(4) | 1010 | offset(24)
615                // wrapping_sub: total under fuzzing (#186), identical for real offsets.
616                let adjusted_offset = offset.wrapping_sub(2); // Account for PC+8
617                let offset_bits = (adjusted_offset as u32) & 0x00FFFFFF;
618                (cond_bits << 28) | 0x0A000000 | offset_bits
619            }
620
621            ArmOp::Bl { label: _ } => {
622                // BL encoding: cond(4) | 1011 | offset(24)
623                0xEB000000
624            }
625
626            ArmOp::Bx { rm } => {
627                let rm_bits = reg_to_bits(rm);
628
629                // BX encoding: cond(4) | 000100101111111111110001 | Rm(4)
630                0xE12FFF10 | rm_bits
631            }
632
633            ArmOp::Blx { rm } => {
634                let rm_bits = reg_to_bits(rm);
635
636                // BLX (register) encoding: cond(4) | 000100101111111111110011 | Rm(4)
637                0xE12FFF30 | rm_bits
638            }
639
640            ArmOp::Push { regs } => {
641                // STMDB SP!, {regs} encoding: cond(4) | 100100 | 10 | 1101 | register_list(16)
642                let mut reg_list: u32 = 0;
643                for r in regs {
644                    reg_list |= 1 << reg_to_bits(r);
645                }
646                0xE92D0000 | reg_list
647            }
648
649            ArmOp::Pop { regs } => {
650                // LDMIA SP!, {regs} encoding: cond(4) | 100010 | 11 | 1101 | register_list(16)
651                let mut reg_list: u32 = 0;
652                for r in regs {
653                    reg_list |= 1 << reg_to_bits(r);
654                }
655                0xE8BD0000 | reg_list
656            }
657
658            ArmOp::Nop => {
659                // NOP encoding: MOV R0, R0
660                0xE1A00000
661            }
662
663            ArmOp::Udf { imm } => {
664                // UDF (Undefined) encoding in ARM: 0xE7F000F0 | (imm12_hi << 8) | imm4_lo
665                // We only use imm8, so split into imm4_hi and imm4_lo
666                let imm8 = *imm as u32;
667                0xE7F000F0 | ((imm8 & 0xF0) << 4) | (imm8 & 0x0F)
668            }
669
670            // Pseudo-instructions for verification - encode as NOP
671            // These are used in formal verification but not actual code generation
672            ArmOp::Popcnt { .. } => {
673                // Population count pseudo-instruction
674                // Not a real ARM instruction, would be expanded to actual code
675                0xE1A00000 // NOP for now
676            }
677
678            ArmOp::SetCond { .. } => {
679                // Condition evaluation pseudo-instruction
680                // Not a real ARM instruction, would be expanded to actual code
681                0xE1A00000 // NOP for now
682            }
683
684            ArmOp::SelectMove { .. } => {
685                // Conditional move pseudo-instruction for ARM32
686                // Would use MOV{cond} instruction
687                0xE1A00000 // NOP for now
688            }
689
690            ArmOp::Select { .. } => {
691                // Select pseudo-instruction
692                // Not a real ARM instruction, would be expanded to conditional moves
693                0xE1A00000 // NOP for now
694            }
695
696            ArmOp::LocalGet { .. } => {
697                // Local variable get pseudo-instruction
698                // Not a real ARM instruction, would be expanded to memory access
699                0xE1A00000 // NOP for now
700            }
701
702            ArmOp::LocalSet { .. } => {
703                // Local variable set pseudo-instruction
704                // Not a real ARM instruction, would be expanded to memory access
705                0xE1A00000 // NOP for now
706            }
707
708            ArmOp::LocalTee { .. } => {
709                // Local variable tee pseudo-instruction
710                // Not a real ARM instruction, would be expanded to memory access
711                0xE1A00000 // NOP for now
712            }
713
714            ArmOp::GlobalGet { .. } => {
715                // Global variable get pseudo-instruction
716                // Not a real ARM instruction, would be expanded to memory access
717                0xE1A00000 // NOP for now
718            }
719
720            ArmOp::GlobalSet { .. } => {
721                // Global variable set pseudo-instruction
722                // Not a real ARM instruction, would be expanded to memory access
723                0xE1A00000 // NOP for now
724            }
725
726            ArmOp::BrTable { .. } => {
727                // Branch table pseudo-instruction
728                // Not a real ARM instruction, would be expanded to jump table
729                0xE1A00000 // NOP for now
730            }
731
732            ArmOp::Call { .. } => {
733                // Function call pseudo-instruction
734                // Not a real ARM instruction, would be expanded to BL
735                0xE1A00000 // NOP for now
736            }
737
738            ArmOp::CallIndirect { .. } => {
739                // Indirect function call pseudo-instruction
740                // Not a real ARM instruction, would be expanded to indirect branch
741                0xE1A00000 // NOP for now
742            }
743
744            // i64 pseudo-instructions (Phase 2) - encode as NOP for now
745            // Real compiler would expand these to multi-instruction sequences
746            ArmOp::I64Add { .. } => 0xE1A00000,        // NOP
747            ArmOp::I64Sub { .. } => 0xE1A00000,        // NOP
748            ArmOp::I64DivS { .. } => 0xE1A00000,       // NOP
749            ArmOp::I64DivU { .. } => 0xE1A00000,       // NOP
750            ArmOp::I64RemS { .. } => 0xE1A00000,       // NOP
751            ArmOp::I64RemU { .. } => 0xE1A00000,       // NOP
752            ArmOp::I64Clz { .. } => 0xE1A00000,        // NOP
753            ArmOp::I64Ctz { .. } => 0xE1A00000,        // NOP
754            ArmOp::I64Popcnt { .. } => 0xE1A00000,     // NOP
755            ArmOp::I64And { .. } => 0xE1A00000,        // NOP
756            ArmOp::I64Or { .. } => 0xE1A00000,         // NOP
757            ArmOp::I64Xor { .. } => 0xE1A00000,        // NOP
758            ArmOp::I64Eqz { .. } => 0xE1A00000,        // NOP
759            ArmOp::I64Eq { .. } => 0xE1A00000,         // NOP
760            ArmOp::I64Ne { .. } => 0xE1A00000,         // NOP
761            ArmOp::I64LtS { .. } => 0xE1A00000,        // NOP
762            ArmOp::I64LtU { .. } => 0xE1A00000,        // NOP
763            ArmOp::I64LeS { .. } => 0xE1A00000,        // NOP
764            ArmOp::I64LeU { .. } => 0xE1A00000,        // NOP
765            ArmOp::I64GtS { .. } => 0xE1A00000,        // NOP
766            ArmOp::I64GtU { .. } => 0xE1A00000,        // NOP
767            ArmOp::I64GeS { .. } => 0xE1A00000,        // NOP
768            ArmOp::I64GeU { .. } => 0xE1A00000,        // NOP
769            ArmOp::I64Const { .. } => 0xE1A00000,      // NOP
770            ArmOp::I64Ldr { .. } => 0xE1A00000,        // NOP
771            ArmOp::I64Str { .. } => 0xE1A00000,        // NOP
772            ArmOp::I64ExtendI32S { .. } => 0xE1A00000, // NOP
773            ArmOp::I64ExtendI32U { .. } => 0xE1A00000, // NOP
774            ArmOp::I64Extend8S { .. } => 0xE1A00000,   // NOP (Thumb-2 only)
775            ArmOp::I64Extend16S { .. } => 0xE1A00000,  // NOP (Thumb-2 only)
776            ArmOp::I64Extend32S { .. } => 0xE1A00000,  // NOP (Thumb-2 only)
777            ArmOp::I32WrapI64 { .. } => 0xE1A00000,    // NOP
778
779            // f32 VFP single-precision instructions
780            ArmOp::F32Add { sd, sn, sm } => encode_vfp_3reg(0xEE300A00, sd, sn, sm)?,
781            ArmOp::F32Sub { sd, sn, sm } => encode_vfp_3reg(0xEE300A40, sd, sn, sm)?,
782            ArmOp::F32Mul { sd, sn, sm } => encode_vfp_3reg(0xEE200A00, sd, sn, sm)?,
783            ArmOp::F32Div { sd, sn, sm } => encode_vfp_3reg(0xEE800A00, sd, sn, sm)?,
784            ArmOp::F32Abs { sd, sm } => encode_vfp_2reg(0xEEB00AC0, sd, sm)?,
785            ArmOp::F32Neg { sd, sm } => encode_vfp_2reg(0xEEB10A40, sd, sm)?,
786            ArmOp::F32Sqrt { sd, sm } => encode_vfp_2reg(0xEEB10AC0, sd, sm)?,
787
788            // f32 pseudo-ops — multi-instruction sequences
789            // FPSCR RMode: 00=nearest, 01=+inf(ceil), 10=-inf(floor), 11=zero(trunc)
790            ArmOp::F32Ceil { sd, sm } => {
791                return self.encode_arm_f32_rounding(sd, sm, 0b01); // Round toward +Inf
792            }
793            ArmOp::F32Floor { sd, sm } => {
794                return self.encode_arm_f32_rounding(sd, sm, 0b10); // Round toward -Inf
795            }
796            ArmOp::F32Trunc { sd, sm } => {
797                return self.encode_arm_f32_rounding(sd, sm, 0b11); // VCVT toward zero
798            }
799            ArmOp::F32Nearest { sd, sm } => {
800                return self.encode_arm_f32_rounding(sd, sm, 0b00); // VCVT to nearest
801            }
802            ArmOp::F32Min { sd, sn, sm } => {
803                return self.encode_arm_f32_minmax(sd, sn, sm, true);
804            }
805            ArmOp::F32Max { sd, sn, sm } => {
806                return self.encode_arm_f32_minmax(sd, sn, sm, false);
807            }
808            ArmOp::F32Copysign { sd, sn, sm } => {
809                return self.encode_arm_f32_copysign(sd, sn, sm);
810            }
811
812            // f32 comparisons — multi-instruction: VCMP + VMRS + conditional MOV
813            ArmOp::F32Eq { rd, sn, sm } => {
814                return self.encode_arm_f32_compare(rd, sn, sm, 0x0); // EQ
815            }
816            ArmOp::F32Ne { rd, sn, sm } => {
817                return self.encode_arm_f32_compare(rd, sn, sm, 0x1); // NE
818            }
819            ArmOp::F32Lt { rd, sn, sm } => {
820                return self.encode_arm_f32_compare(rd, sn, sm, 0x4); // MI (less than)
821            }
822            ArmOp::F32Le { rd, sn, sm } => {
823                return self.encode_arm_f32_compare(rd, sn, sm, 0x9); // LS (less or same)
824            }
825            ArmOp::F32Gt { rd, sn, sm } => {
826                return self.encode_arm_f32_compare(rd, sn, sm, 0xC); // GT
827            }
828            ArmOp::F32Ge { rd, sn, sm } => {
829                return self.encode_arm_f32_compare(rd, sn, sm, 0xA); // GE
830            }
831
832            // f32 const — multi-instruction: MOVW + MOVT + VMOV
833            ArmOp::F32Const { sd, value } => {
834                return self.encode_arm_f32_const(sd, *value);
835            }
836
837            ArmOp::F32Load { sd, addr } => encode_vfp_ldst(0xED900A00, sd, addr)?,
838            ArmOp::F32Store { sd, addr } => encode_vfp_ldst(0xED800A00, sd, addr)?,
839
840            // f32 conversions — multi-instruction sequences
841            ArmOp::F32ConvertI32S { sd, rm } => {
842                return self.encode_arm_f32_convert_i32(sd, rm, true);
843            }
844            ArmOp::F32ConvertI32U { sd, rm } => {
845                return self.encode_arm_f32_convert_i32(sd, rm, false);
846            }
847            ArmOp::F32ConvertI64S { .. } | ArmOp::F32ConvertI64U { .. } => {
848                return Err(synth_core::Error::synthesis(
849                    "F32 i64 conversion not supported (requires register pairs on 32-bit ARM)",
850                ));
851            }
852            ArmOp::F32ReinterpretI32 { sd, rm } => encode_vmov_core_sreg(true, sd, rm)?,
853            ArmOp::I32ReinterpretF32 { rd, sm } => encode_vmov_core_sreg(false, sm, rd)?,
854            ArmOp::I32TruncF32S { rd, sm } => {
855                return self.encode_arm_i32_trunc_f32(rd, sm, true);
856            }
857            ArmOp::I32TruncF32U { rd, sm } => {
858                return self.encode_arm_i32_trunc_f32(rd, sm, false);
859            }
860
861            // f64 VFP double-precision instructions (ARM32)
862            // F64 arithmetic: same as F32 but with sz=1 (bit 8 = 1, cp11 = 0xB)
863            ArmOp::F64Add { dd, dn, dm } => encode_vfp_3reg_f64(0xEE300B00, dd, dn, dm)?,
864            ArmOp::F64Sub { dd, dn, dm } => encode_vfp_3reg_f64(0xEE300B40, dd, dn, dm)?,
865            ArmOp::F64Mul { dd, dn, dm } => encode_vfp_3reg_f64(0xEE200B00, dd, dn, dm)?,
866            ArmOp::F64Div { dd, dn, dm } => encode_vfp_3reg_f64(0xEE800B00, dd, dn, dm)?,
867            ArmOp::F64Abs { dd, dm } => encode_vfp_2reg_f64(0xEEB00BC0, dd, dm)?,
868            ArmOp::F64Neg { dd, dm } => encode_vfp_2reg_f64(0xEEB10B40, dd, dm)?,
869            ArmOp::F64Sqrt { dd, dm } => encode_vfp_2reg_f64(0xEEB10BC0, dd, dm)?,
870
871            // f64 pseudo-ops
872            // FPSCR RMode: 00=nearest, 01=+inf(ceil), 10=-inf(floor), 11=zero(trunc)
873            ArmOp::F64Ceil { dd, dm } => {
874                return self.encode_arm_f64_rounding(dd, dm, 0b01);
875            }
876            ArmOp::F64Floor { dd, dm } => {
877                return self.encode_arm_f64_rounding(dd, dm, 0b10);
878            }
879            ArmOp::F64Trunc { dd, dm } => {
880                return self.encode_arm_f64_rounding(dd, dm, 0b11);
881            }
882            ArmOp::F64Nearest { dd, dm } => {
883                return self.encode_arm_f64_rounding(dd, dm, 0b00);
884            }
885            ArmOp::F64Min { dd, dn, dm } => {
886                return self.encode_arm_f64_minmax(dd, dn, dm, true);
887            }
888            ArmOp::F64Max { dd, dn, dm } => {
889                return self.encode_arm_f64_minmax(dd, dn, dm, false);
890            }
891            ArmOp::F64Copysign { dd, dn, dm } => {
892                return self.encode_arm_f64_copysign(dd, dn, dm);
893            }
894
895            // f64 comparisons
896            ArmOp::F64Eq { rd, dn, dm } => {
897                return self.encode_arm_f64_compare(rd, dn, dm, 0x0);
898            }
899            ArmOp::F64Ne { rd, dn, dm } => {
900                return self.encode_arm_f64_compare(rd, dn, dm, 0x1);
901            }
902            ArmOp::F64Lt { rd, dn, dm } => {
903                return self.encode_arm_f64_compare(rd, dn, dm, 0x4);
904            }
905            ArmOp::F64Le { rd, dn, dm } => {
906                return self.encode_arm_f64_compare(rd, dn, dm, 0x9);
907            }
908            ArmOp::F64Gt { rd, dn, dm } => {
909                return self.encode_arm_f64_compare(rd, dn, dm, 0xC);
910            }
911            ArmOp::F64Ge { rd, dn, dm } => {
912                return self.encode_arm_f64_compare(rd, dn, dm, 0xA);
913            }
914
915            ArmOp::F64Const { dd, value } => {
916                return self.encode_arm_f64_const(dd, *value);
917            }
918
919            ArmOp::F64Load { dd, addr } => encode_vfp_ldst_f64(0xED900B00, dd, addr)?,
920            ArmOp::F64Store { dd, addr } => encode_vfp_ldst_f64(0xED800B00, dd, addr)?,
921
922            ArmOp::F64ConvertI32S { dd, rm } => {
923                return self.encode_arm_f64_convert_i32(dd, rm, true);
924            }
925            ArmOp::F64ConvertI32U { dd, rm } => {
926                return self.encode_arm_f64_convert_i32(dd, rm, false);
927            }
928            ArmOp::F64ConvertI64S { .. } | ArmOp::F64ConvertI64U { .. } => {
929                return Err(synth_core::Error::synthesis(
930                    "F64 i64 conversion not supported (requires register pairs on 32-bit ARM)",
931                ));
932            }
933            ArmOp::F64PromoteF32 { dd, sm } => {
934                return self.encode_arm_f64_promote_f32(dd, sm);
935            }
936            ArmOp::F64ReinterpretI64 { dd, rmlo, rmhi } => {
937                encode_vmov_core_dreg(true, dd, rmlo, rmhi)?
938            }
939            ArmOp::I64ReinterpretF64 { rdlo, rdhi, dm } => {
940                encode_vmov_core_dreg(false, dm, rdlo, rdhi)?
941            }
942            ArmOp::I64TruncF64S { .. } | ArmOp::I64TruncF64U { .. } => {
943                return Err(synth_core::Error::synthesis(
944                    "i64 truncation from F64 not supported (requires i64 register pairs on 32-bit ARM)",
945                ));
946            }
947            ArmOp::I32TruncF64S { rd, dm } => {
948                return self.encode_arm_i32_trunc_f64(rd, dm, true);
949            }
950            ArmOp::I32TruncF64U { rd, dm } => {
951                return self.encode_arm_i32_trunc_f64(rd, dm, false);
952            }
953            // Multi-instruction sequences - only meaningful in Thumb-2 mode
954            ArmOp::I64SetCond { .. }
955            | ArmOp::I64SetCondZ { .. }
956            | ArmOp::I64Mul { .. }
957            | ArmOp::I64Shl { .. }
958            | ArmOp::I64ShrS { .. }
959            | ArmOp::I64ShrU { .. }
960            | ArmOp::I64Rotl { .. }
961            | ArmOp::I64Rotr { .. } => 0xE1A00000, // NOP (Thumb-2 only)
962
963            // MVE instructions — Thumb-2 only (Cortex-M55 is always Thumb-2)
964            ArmOp::MveLoad { .. }
965            | ArmOp::MveStore { .. }
966            | ArmOp::MveConst { .. }
967            | ArmOp::MveAnd { .. }
968            | ArmOp::MveOrr { .. }
969            | ArmOp::MveEor { .. }
970            | ArmOp::MveMvn { .. }
971            | ArmOp::MveBic { .. }
972            | ArmOp::MveAddI { .. }
973            | ArmOp::MveSubI { .. }
974            | ArmOp::MveMulI { .. }
975            | ArmOp::MveNegI { .. }
976            | ArmOp::MveCmpEqI { .. }
977            | ArmOp::MveCmpNeI { .. }
978            | ArmOp::MveCmpLtS { .. }
979            | ArmOp::MveCmpLtU { .. }
980            | ArmOp::MveCmpGtS { .. }
981            | ArmOp::MveCmpGtU { .. }
982            | ArmOp::MveCmpLeS { .. }
983            | ArmOp::MveCmpLeU { .. }
984            | ArmOp::MveCmpGeS { .. }
985            | ArmOp::MveCmpGeU { .. }
986            | ArmOp::MveDup { .. }
987            | ArmOp::MveExtractLane { .. }
988            | ArmOp::MveInsertLane { .. }
989            | ArmOp::MveAddF32 { .. }
990            | ArmOp::MveSubF32 { .. }
991            | ArmOp::MveMulF32 { .. }
992            | ArmOp::MveNegF32 { .. }
993            | ArmOp::MveAbsF32 { .. }
994            | ArmOp::MveCmpEqF32 { .. }
995            | ArmOp::MveCmpNeF32 { .. }
996            | ArmOp::MveCmpLtF32 { .. }
997            | ArmOp::MveCmpLeF32 { .. }
998            | ArmOp::MveCmpGtF32 { .. }
999            | ArmOp::MveCmpGeF32 { .. }
1000            | ArmOp::MveDupF32 { .. }
1001            | ArmOp::MveExtractLaneF32 { .. }
1002            | ArmOp::MveReplaceLaneF32 { .. }
1003            | ArmOp::MveDivF32 { .. }
1004            | ArmOp::MveSqrtF32 { .. } => 0xE1A00000, // NOP (MVE = Thumb-2 only)
1005        };
1006
1007        // ARM32 instructions are little-endian
1008        Ok(instr.to_le_bytes().to_vec())
1009    }
1010
1011    // === ARM32 VFP multi-instruction helpers ===
1012
1013    /// Encode F32 comparison as ARM32: VCMP.F32 + VMRS + MOV rd,#0 + MOVcond rd,#1
1014    fn encode_arm_f32_compare(
1015        &self,
1016        rd: &Reg,
1017        sn: &VfpReg,
1018        sm: &VfpReg,
1019        cond_code: u32,
1020    ) -> Result<Vec<u8>> {
1021        let mut bytes = Vec::new();
1022
1023        // VCMP.F32 Sn, Sm: 0xEEB40A40 with Sn in Vd position, Sm in Vm position
1024        let sn_num = vfp_sreg_to_num(sn)?;
1025        let sm_num = vfp_sreg_to_num(sm)?;
1026        let (vd, d) = encode_sreg(sn_num);
1027        let (vm, m) = encode_sreg(sm_num);
1028        let vcmp = 0xEEB40A40 | (d << 22) | (vd << 12) | (m << 5) | vm;
1029        bytes.extend_from_slice(&vcmp.to_le_bytes());
1030
1031        // VMRS APSR_nzcv, FPSCR: 0xEEF1FA10
1032        bytes.extend_from_slice(&0xEEF1FA10u32.to_le_bytes());
1033
1034        // MOV rd, #0: 0xE3A0_0000 | (rd << 12)
1035        let rd_bits = reg_to_bits(rd);
1036        let mov_zero = 0xE3A00000 | (rd_bits << 12);
1037        bytes.extend_from_slice(&mov_zero.to_le_bytes());
1038
1039        // MOVcond rd, #1: cond(4) | 0011 1010 0000 rd(4) 0000 0000 0001
1040        let mov_one = (cond_code << 28) | 0x03A00001 | (rd_bits << 12);
1041        bytes.extend_from_slice(&mov_one.to_le_bytes());
1042
1043        Ok(bytes)
1044    }
1045
1046    /// Encode F32 constant load as ARM32: MOVW Rt,#lo16 + MOVT Rt,#hi16 + VMOV Sd,Rt
1047    fn encode_arm_f32_const(&self, sd: &VfpReg, value: f32) -> Result<Vec<u8>> {
1048        let mut bytes = Vec::new();
1049        let bits = value.to_bits();
1050
1051        // Use R12 as temp register for constant loading
1052        let rt: u32 = 12; // R12/IP
1053
1054        // MOVW R12, #lo16: 0xE300_C000 | (imm4 << 16) | imm12
1055        let lo16 = bits & 0xFFFF;
1056        let movw = 0xE3000000 | (rt << 12) | ((lo16 >> 12) << 16) | (lo16 & 0xFFF);
1057        bytes.extend_from_slice(&movw.to_le_bytes());
1058
1059        // MOVT R12, #hi16: 0xE340_C000 | (imm4 << 16) | imm12
1060        let hi16 = (bits >> 16) & 0xFFFF;
1061        let movt = 0xE3400000 | (rt << 12) | ((hi16 >> 12) << 16) | (hi16 & 0xFFF);
1062        bytes.extend_from_slice(&movt.to_le_bytes());
1063
1064        // VMOV Sd, R12
1065        let vmov = encode_vmov_core_sreg(true, sd, &Reg::R12)?;
1066        bytes.extend_from_slice(&vmov.to_le_bytes());
1067
1068        Ok(bytes)
1069    }
1070
1071    /// Encode VMOV + VCVT.F32.S32/U32 as ARM32
1072    fn encode_arm_f32_convert_i32(&self, sd: &VfpReg, rm: &Reg, signed: bool) -> Result<Vec<u8>> {
1073        let mut bytes = Vec::new();
1074
1075        // VMOV Sd, Rm — move integer to VFP register
1076        let vmov = encode_vmov_core_sreg(true, sd, rm)?;
1077        bytes.extend_from_slice(&vmov.to_le_bytes());
1078
1079        // VCVT.F32.S32 Sd, Sd (signed) or VCVT.F32.U32 Sd, Sd (unsigned)
1080        // Base: 0xEEB80A40 (signed) or 0xEEB80AC0 (unsigned)
1081        let sd_num = vfp_sreg_to_num(sd)?;
1082        let (vd, d) = encode_sreg(sd_num);
1083        let (vm, m) = encode_sreg(sd_num); // same register as source
1084        let base = if signed { 0xEEB80A40 } else { 0xEEB80AC0 };
1085        let vcvt = base | (d << 22) | (vd << 12) | (m << 5) | vm;
1086        bytes.extend_from_slice(&vcvt.to_le_bytes());
1087
1088        Ok(bytes)
1089    }
1090
1091    /// Encode F32 rounding pseudo-op as ARM32 via VCVT to integer and back.
1092    /// mode: 0b00=nearest, 0b01=floor(-Inf), 0b10=ceil(+Inf), 0b11=trunc(zero)
1093    /// Strategy: VCVT.S32.F32 Sd, Sm (toward zero), then VCVT.F32.S32 Sd, Sd
1094    /// For ceil/floor/nearest, we use VCVTR (round toward mode) + convert back.
1095    /// Simplified: convert to int (toward zero for trunc) then back to float.
1096    /// Encode F32 rounding as ARM32.
1097    /// `mode`: FPSCR RMode — 0b00=nearest, 0b01=+inf(ceil), 0b10=-inf(floor), 0b11=zero(trunc)
1098    ///
1099    /// For trunc (mode=0b11): uses VCVTR.S32.F32 (always rounds toward zero).
1100    /// For ceil/floor/nearest: sets FPSCR rounding mode, uses VCVT.S32.F32 (non-R variant
1101    /// which honours FPSCR rmode), then restores FPSCR.
1102    fn encode_arm_f32_rounding(&self, sd: &VfpReg, sm: &VfpReg, mode: u8) -> Result<Vec<u8>> {
1103        let mut bytes = Vec::new();
1104        let sm_num = vfp_sreg_to_num(sm)?;
1105        let sd_num = vfp_sreg_to_num(sd)?;
1106        let (vd_s, d_s) = encode_sreg(sd_num);
1107        let (vm_s, m_s) = encode_sreg(sm_num);
1108
1109        if mode == 0b11 {
1110            // Trunc (toward zero): VCVTR.S32.F32 — the "R" variant always truncates.
1111            // 0xEEBD0AC0: bit[7]=1 => round toward zero regardless of FPSCR
1112            let vcvt_to_int = 0xEEBD0AC0 | (d_s << 22) | (vd_s << 12) | (m_s << 5) | vm_s;
1113            bytes.extend_from_slice(&vcvt_to_int.to_le_bytes());
1114        } else {
1115            // ceil/floor/nearest: manipulate FPSCR rounding mode
1116            let rt: u32 = 12; // R12/IP as temp
1117
1118            // VMRS R12, FPSCR
1119            let vmrs = 0xEEF10A10 | (rt << 12);
1120            bytes.extend_from_slice(&vmrs.to_le_bytes());
1121
1122            // BIC R12, R12, #(3 << 22) — clear RMode bits [23:22]
1123            // 3<<22 = 0x00C00000. ARM rotated imm: 0x03 ror 10 (rotation=5, imm8=0x03)
1124            let bic = 0xE3CC0000 | (rt << 12) | (0x05 << 8) | 0x03;
1125            bytes.extend_from_slice(&bic.to_le_bytes());
1126
1127            // ORR R12, R12, #(mode << 22) — set desired rounding mode
1128            if mode != 0 {
1129                // mode<<22: rotation=5, imm8=mode
1130                let orr = 0xE38C0000 | (rt << 12) | (0x05 << 8) | (mode as u32);
1131                bytes.extend_from_slice(&orr.to_le_bytes());
1132            }
1133
1134            // VMSR FPSCR, R12
1135            let vmsr = 0xEEE10A10 | (rt << 12);
1136            bytes.extend_from_slice(&vmsr.to_le_bytes());
1137
1138            // VCVT.S32.F32 Sd, Sm — non-R variant (bit[7]=0), uses FPSCR rounding mode
1139            let vcvt_to_int = 0xEEBD0A40 | (d_s << 22) | (vd_s << 12) | (m_s << 5) | vm_s;
1140            bytes.extend_from_slice(&vcvt_to_int.to_le_bytes());
1141
1142            // Restore FPSCR: clear rmode bits back to nearest (default)
1143            bytes.extend_from_slice(&vmrs.to_le_bytes());
1144            bytes.extend_from_slice(&bic.to_le_bytes());
1145            bytes.extend_from_slice(&vmsr.to_le_bytes());
1146        }
1147
1148        // VCVT.F32.S32 Sd, Sd (convert integer result back to float)
1149        let (vd2, d2) = encode_sreg(sd_num);
1150        let vcvt_to_float = 0xEEB80A40 | (d2 << 22) | (vd2 << 12) | (d_s << 5) | vd_s;
1151        bytes.extend_from_slice(&vcvt_to_float.to_le_bytes());
1152
1153        Ok(bytes)
1154    }
1155
1156    /// Encode F32 min/max as ARM32: VCMP + VMRS + conditional VMOV
1157    fn encode_arm_f32_minmax(
1158        &self,
1159        sd: &VfpReg,
1160        sn: &VfpReg,
1161        sm: &VfpReg,
1162        is_min: bool,
1163    ) -> Result<Vec<u8>> {
1164        let mut bytes = Vec::new();
1165        let sn_num = vfp_sreg_to_num(sn)?;
1166        let sm_num = vfp_sreg_to_num(sm)?;
1167        let sd_num = vfp_sreg_to_num(sd)?;
1168
1169        // VMOV Sd, Sn (start with first operand)
1170        let (vd, d) = encode_sreg(sd_num);
1171        let (vn, n) = encode_sreg(sn_num);
1172        let vmov_sn = 0xEEB00A40 | (d << 22) | (vd << 12) | (n << 5) | vn;
1173        bytes.extend_from_slice(&vmov_sn.to_le_bytes());
1174
1175        // VCMP.F32 Sn, Sm
1176        let (vm, m) = encode_sreg(sm_num);
1177        let vcmp = 0xEEB40A40 | (n << 22) | (vn << 12) | (m << 5) | vm;
1178        bytes.extend_from_slice(&vcmp.to_le_bytes());
1179
1180        // VMRS APSR_nzcv, FPSCR
1181        bytes.extend_from_slice(&0xEEF1FA10u32.to_le_bytes());
1182
1183        // For min: if Sn > Sm (GT), use Sm. Condition = GT (0xC)
1184        // For max: if Sn < Sm (MI/LT), use Sm. Condition = MI (0x4)
1185        let cond = if is_min { 0xCu32 } else { 0x4u32 };
1186
1187        // VMOV{cond} Sd, Sm — conditional VMOV
1188        let vmov_cond = (cond << 28) | 0x0EB00A40 | (d << 22) | (vd << 12) | (m << 5) | vm;
1189        bytes.extend_from_slice(&vmov_cond.to_le_bytes());
1190
1191        Ok(bytes)
1192    }
1193
1194    /// Encode F32 copysign as ARM32: extract sign from Sm, magnitude from Sn
1195    fn encode_arm_f32_copysign(&self, sd: &VfpReg, sn: &VfpReg, sm: &VfpReg) -> Result<Vec<u8>> {
1196        let mut bytes = Vec::new();
1197
1198        // VMOV R12, Sm (get sign source bits)
1199        let vmov_sm = encode_vmov_core_sreg(false, sm, &Reg::R12)?;
1200        bytes.extend_from_slice(&vmov_sm.to_le_bytes());
1201
1202        // VMOV R0, Sn (get magnitude source bits) — use R0 as temp
1203        let vmov_sn = encode_vmov_core_sreg(false, sn, &Reg::R0)?;
1204        bytes.extend_from_slice(&vmov_sn.to_le_bytes());
1205
1206        // AND R12, R12, #0x80000000 (keep only sign bit)
1207        // Thumb-2 constant 0x80000000 needs special encoding; in ARM32 use rotated imm
1208        // 0x80000000 = 0x02 rotated right by 2 (rotation=1, imm8=0x02)
1209        let and_sign = 0xE2000000u32 | (12 << 16) | (12 << 12) | (1 << 8) | 0x02;
1210        bytes.extend_from_slice(&and_sign.to_le_bytes());
1211
1212        // BIC R0, R0, #0x80000000 (clear sign bit from magnitude)
1213        // R0 = register 0, so Rn and Rd fields are 0
1214        let bic_sign = 0xE3C00000u32 | (1 << 8) | 0x02;
1215        bytes.extend_from_slice(&bic_sign.to_le_bytes());
1216
1217        // ORR R0, R0, R12 (combine sign + magnitude)
1218        // R0 = register 0, so Rn and Rd fields are 0
1219        let orr = 0xE1800000u32 | 12;
1220        bytes.extend_from_slice(&orr.to_le_bytes());
1221
1222        // VMOV Sd, R0
1223        let vmov_result = encode_vmov_core_sreg(true, sd, &Reg::R0)?;
1224        bytes.extend_from_slice(&vmov_result.to_le_bytes());
1225
1226        Ok(bytes)
1227    }
1228
1229    /// Encode F64 comparison as ARM32: VCMP.F64 + VMRS + MOV rd,#0 + MOVcond rd,#1
1230    fn encode_arm_f64_compare(
1231        &self,
1232        rd: &Reg,
1233        dn: &VfpReg,
1234        dm: &VfpReg,
1235        cond_code: u32,
1236    ) -> Result<Vec<u8>> {
1237        let mut bytes = Vec::new();
1238
1239        // VCMP.F64 Dn, Dm: 0xEEB40B40 with Dn in Vd position, Dm in Vm position
1240        let dn_num = vfp_dreg_to_num(dn)?;
1241        let dm_num = vfp_dreg_to_num(dm)?;
1242        let (vd, d) = encode_dreg(dn_num);
1243        let (vm, m) = encode_dreg(dm_num);
1244        let vcmp = 0xEEB40B40 | (d << 22) | (vd << 12) | (m << 5) | vm;
1245        bytes.extend_from_slice(&vcmp.to_le_bytes());
1246
1247        // VMRS APSR_nzcv, FPSCR
1248        bytes.extend_from_slice(&0xEEF1FA10u32.to_le_bytes());
1249
1250        // MOV rd, #0
1251        let rd_bits = reg_to_bits(rd);
1252        let mov_zero = 0xE3A00000 | (rd_bits << 12);
1253        bytes.extend_from_slice(&mov_zero.to_le_bytes());
1254
1255        // MOVcond rd, #1
1256        let mov_one = (cond_code << 28) | 0x03A00001 | (rd_bits << 12);
1257        bytes.extend_from_slice(&mov_one.to_le_bytes());
1258
1259        Ok(bytes)
1260    }
1261
1262    /// Encode F64 constant load as ARM32: MOVW + MOVT + MOVW + MOVT + VMOV
1263    fn encode_arm_f64_const(&self, dd: &VfpReg, value: f64) -> Result<Vec<u8>> {
1264        let mut bytes = Vec::new();
1265        let bits = value.to_bits();
1266        let lo32 = bits as u32;
1267        let hi32 = (bits >> 32) as u32;
1268
1269        // Load low 32 bits into R0 (Rd field = 0 for R0)
1270        let lo16 = lo32 & 0xFFFF;
1271        let movw_r0 = 0xE3000000 | ((lo16 >> 12) << 16) | (lo16 & 0xFFF);
1272        bytes.extend_from_slice(&movw_r0.to_le_bytes());
1273        let hi16 = (lo32 >> 16) & 0xFFFF;
1274        let movt_r0 = 0xE3400000 | ((hi16 >> 12) << 16) | (hi16 & 0xFFF);
1275        bytes.extend_from_slice(&movt_r0.to_le_bytes());
1276
1277        // Load high 32 bits into R12
1278        let lo16 = hi32 & 0xFFFF;
1279        let movw_r12 = 0xE3000000 | ((lo16 >> 12) << 16) | (12 << 12) | (lo16 & 0xFFF);
1280        bytes.extend_from_slice(&movw_r12.to_le_bytes());
1281        let hi16 = (hi32 >> 16) & 0xFFFF;
1282        let movt_r12 = 0xE3400000 | ((hi16 >> 12) << 16) | (12 << 12) | (hi16 & 0xFFF);
1283        bytes.extend_from_slice(&movt_r12.to_le_bytes());
1284
1285        // VMOV Dd, R0, R12
1286        let vmov = encode_vmov_core_dreg(true, dd, &Reg::R0, &Reg::R12)?;
1287        bytes.extend_from_slice(&vmov.to_le_bytes());
1288
1289        Ok(bytes)
1290    }
1291
1292    /// Encode VMOV Sd, Rm + VCVT.F64.S32/U32 Dd, Sd as ARM32
1293    fn encode_arm_f64_convert_i32(&self, dd: &VfpReg, rm: &Reg, signed: bool) -> Result<Vec<u8>> {
1294        let mut bytes = Vec::new();
1295
1296        // Use S0 as intermediate: VMOV S0, Rm
1297        let vmov = encode_vmov_core_sreg(true, &VfpReg::S0, rm)?;
1298        bytes.extend_from_slice(&vmov.to_le_bytes());
1299
1300        // VCVT.F64.S32 Dd, S0 (signed) or VCVT.F64.U32 Dd, S0 (unsigned)
1301        // Base: 0xEEB80B40 (signed) or 0xEEB80BC0 (unsigned)
1302        let dd_num = vfp_dreg_to_num(dd)?;
1303        let (vd, d) = encode_dreg(dd_num);
1304        let base = if signed { 0xEEB80B40 } else { 0xEEB80BC0 };
1305        // S0 is register 0: Vm=0, M=0
1306        let vcvt = base | (d << 22) | (vd << 12);
1307        bytes.extend_from_slice(&vcvt.to_le_bytes());
1308
1309        Ok(bytes)
1310    }
1311
1312    /// Encode VCVT.F64.F32 Dd, Sm as ARM32 (f32 to f64 promotion)
1313    fn encode_arm_f64_promote_f32(&self, dd: &VfpReg, sm: &VfpReg) -> Result<Vec<u8>> {
1314        let dd_num = vfp_dreg_to_num(dd)?;
1315        let sm_num = vfp_sreg_to_num(sm)?;
1316        let (vd, d) = encode_dreg(dd_num);
1317        let (vm, m) = encode_sreg(sm_num);
1318
1319        // VCVT.F64.F32 Dd, Sm: 0xEEB70AC0
1320        let vcvt = 0xEEB70AC0 | (d << 22) | (vd << 12) | (m << 5) | vm;
1321        Ok(vcvt.to_le_bytes().to_vec())
1322    }
1323
1324    /// Encode VCVT.S32/U32.F64 Sd, Dm + VMOV Rd, Sd as ARM32
1325    fn encode_arm_i32_trunc_f64(&self, rd: &Reg, dm: &VfpReg, signed: bool) -> Result<Vec<u8>> {
1326        let mut bytes = Vec::new();
1327        let dm_num = vfp_dreg_to_num(dm)?;
1328        let (vm, m) = encode_dreg(dm_num);
1329
1330        // VCVT.S32.F64 S0, Dm (toward zero) or VCVT.U32.F64 S0, Dm
1331        // S0: Vd=0, D=0
1332        let base = if signed { 0xEEBD0BC0 } else { 0xEEBC0BC0 };
1333        let vcvt = base | (m << 5) | vm;
1334        bytes.extend_from_slice(&vcvt.to_le_bytes());
1335
1336        // VMOV Rd, S0
1337        let vmov = encode_vmov_core_sreg(false, &VfpReg::S0, rd)?;
1338        bytes.extend_from_slice(&vmov.to_le_bytes());
1339
1340        Ok(bytes)
1341    }
1342
1343    /// Encode F64 rounding pseudo-op as ARM32 via VCVT to integer and back.
1344    /// Encode F64 rounding as ARM32.
1345    /// `mode`: FPSCR RMode — 0b00=nearest, 0b01=+inf(ceil), 0b10=-inf(floor), 0b11=zero(trunc)
1346    ///
1347    /// For trunc: uses VCVTR.S32.F64 (always truncates).
1348    /// For ceil/floor/nearest: sets FPSCR rounding mode, uses VCVT.S32.F64 (non-R variant),
1349    /// then restores FPSCR.
1350    fn encode_arm_f64_rounding(&self, dd: &VfpReg, dm: &VfpReg, mode: u8) -> Result<Vec<u8>> {
1351        let mut bytes = Vec::new();
1352        let dm_num = vfp_dreg_to_num(dm)?;
1353        let dd_num = vfp_dreg_to_num(dd)?;
1354        let (vm, m) = encode_dreg(dm_num);
1355        let (vd, d) = encode_dreg(dd_num);
1356
1357        if mode == 0b11 {
1358            // Trunc (toward zero): VCVTR.S32.F64 — bit[7]=1, always truncates
1359            let vcvt_to_int = 0xEEBD0BC0 | (m << 5) | vm;
1360            bytes.extend_from_slice(&vcvt_to_int.to_le_bytes());
1361        } else {
1362            // ceil/floor/nearest: manipulate FPSCR rounding mode
1363            let rt: u32 = 12;
1364
1365            // VMRS R12, FPSCR
1366            let vmrs = 0xEEF10A10 | (rt << 12);
1367            bytes.extend_from_slice(&vmrs.to_le_bytes());
1368
1369            // BIC R12, R12, #(3 << 22)
1370            let bic = 0xE3CC0000 | (rt << 12) | (0x05 << 8) | 0x03;
1371            bytes.extend_from_slice(&bic.to_le_bytes());
1372
1373            // ORR R12, R12, #(mode << 22)
1374            if mode != 0 {
1375                let orr = 0xE38C0000 | (rt << 12) | (0x05 << 8) | (mode as u32);
1376                bytes.extend_from_slice(&orr.to_le_bytes());
1377            }
1378
1379            // VMSR FPSCR, R12
1380            let vmsr = 0xEEE10A10 | (rt << 12);
1381            bytes.extend_from_slice(&vmsr.to_le_bytes());
1382
1383            // VCVT.S32.F64 S0, Dm — non-R variant (bit[7]=0), uses FPSCR rmode
1384            let vcvt_to_int = 0xEEBD0B40 | (m << 5) | vm;
1385            bytes.extend_from_slice(&vcvt_to_int.to_le_bytes());
1386
1387            // Restore FPSCR
1388            bytes.extend_from_slice(&vmrs.to_le_bytes());
1389            bytes.extend_from_slice(&bic.to_le_bytes());
1390            bytes.extend_from_slice(&vmsr.to_le_bytes());
1391        }
1392
1393        // VCVT.F64.S32 Dd, S0 (convert back to double)
1394        let vcvt_to_float = 0xEEB80B40 | (d << 22) | (vd << 12);
1395        bytes.extend_from_slice(&vcvt_to_float.to_le_bytes());
1396
1397        Ok(bytes)
1398    }
1399
1400    /// Encode F64 min/max as ARM32: VMOV + VCMP + VMRS + conditional VMOV
1401    fn encode_arm_f64_minmax(
1402        &self,
1403        dd: &VfpReg,
1404        dn: &VfpReg,
1405        dm: &VfpReg,
1406        is_min: bool,
1407    ) -> Result<Vec<u8>> {
1408        let mut bytes = Vec::new();
1409        let dn_num = vfp_dreg_to_num(dn)?;
1410        let dm_num = vfp_dreg_to_num(dm)?;
1411        let dd_num = vfp_dreg_to_num(dd)?;
1412
1413        // VMOV.F64 Dd, Dn (start with first operand)
1414        let (vd, d) = encode_dreg(dd_num);
1415        let (vn, n) = encode_dreg(dn_num);
1416        let vmov_dn = 0xEEB00B40 | (d << 22) | (vd << 12) | (n << 5) | vn;
1417        bytes.extend_from_slice(&vmov_dn.to_le_bytes());
1418
1419        // VCMP.F64 Dn, Dm
1420        let (vm, m) = encode_dreg(dm_num);
1421        let vcmp = 0xEEB40B40 | (n << 22) | (vn << 12) | (m << 5) | vm;
1422        bytes.extend_from_slice(&vcmp.to_le_bytes());
1423
1424        // VMRS APSR_nzcv, FPSCR
1425        bytes.extend_from_slice(&0xEEF1FA10u32.to_le_bytes());
1426
1427        let cond = if is_min { 0xCu32 } else { 0x4u32 };
1428        let vmov_cond = (cond << 28) | 0x0EB00B40 | (d << 22) | (vd << 12) | (m << 5) | vm;
1429        bytes.extend_from_slice(&vmov_cond.to_le_bytes());
1430
1431        Ok(bytes)
1432    }
1433
1434    /// Encode F64 copysign as ARM32
1435    fn encode_arm_f64_copysign(&self, dd: &VfpReg, dn: &VfpReg, dm: &VfpReg) -> Result<Vec<u8>> {
1436        let mut bytes = Vec::new();
1437
1438        // VMOV R0, R12, Dm (get sign source bits)
1439        let vmov_dm = encode_vmov_core_dreg(false, dm, &Reg::R0, &Reg::R12)?;
1440        bytes.extend_from_slice(&vmov_dm.to_le_bytes());
1441
1442        // VMOV R1, R2, Dn (get magnitude source bits)
1443        // We use R1 (lo) and R2 (hi) for the magnitude
1444        let vmov_dn = encode_vmov_core_dreg(false, dn, &Reg::R1, &Reg::R2)?;
1445        bytes.extend_from_slice(&vmov_dn.to_le_bytes());
1446
1447        // AND R12, R12, #0x80000000 (keep only sign bit from hi word)
1448        let and_sign = 0xE2000000u32 | (12 << 16) | (12 << 12) | (1 << 8) | 0x02;
1449        bytes.extend_from_slice(&and_sign.to_le_bytes());
1450
1451        // BIC R2, R2, #0x80000000 (clear sign bit from magnitude hi word)
1452        let bic_sign = 0xE3C00000u32 | (2 << 16) | (2 << 12) | (1 << 8) | 0x02;
1453        bytes.extend_from_slice(&bic_sign.to_le_bytes());
1454
1455        // ORR R2, R2, R12 (combine sign + magnitude)
1456        let orr = 0xE1800000u32 | (2 << 16) | (2 << 12) | 12;
1457        bytes.extend_from_slice(&orr.to_le_bytes());
1458
1459        // VMOV Dd, R1, R2
1460        let vmov_result = encode_vmov_core_dreg(true, dd, &Reg::R1, &Reg::R2)?;
1461        bytes.extend_from_slice(&vmov_result.to_le_bytes());
1462
1463        Ok(bytes)
1464    }
1465
1466    /// Encode VCVT.S32/U32.F32 + VMOV as ARM32
1467    fn encode_arm_i32_trunc_f32(&self, rd: &Reg, sm: &VfpReg, signed: bool) -> Result<Vec<u8>> {
1468        let mut bytes = Vec::new();
1469
1470        // VCVT.S32.F32 Sd, Sm (toward zero) or VCVT.U32.F32 Sd, Sm
1471        // We use Sm as both source and destination for the intermediate result
1472        let sm_num = vfp_sreg_to_num(sm)?;
1473        let (vd, d) = encode_sreg(sm_num);
1474        let (vm, m) = encode_sreg(sm_num);
1475        let base = if signed { 0xEEBD0AC0 } else { 0xEEBC0AC0 };
1476        let vcvt = base | (d << 22) | (vd << 12) | (m << 5) | vm;
1477        bytes.extend_from_slice(&vcvt.to_le_bytes());
1478
1479        // VMOV Rd, Sm — move result back to core register
1480        let vmov = encode_vmov_core_sreg(false, sm, rd)?;
1481        bytes.extend_from_slice(&vmov.to_le_bytes());
1482
1483        Ok(bytes)
1484    }
1485
1486    /// Encode an ARM instruction in Thumb-2 mode (16-bit or 32-bit instructions)
1487    fn encode_thumb(&self, op: &ArmOp) -> Result<Vec<u8>> {
1488        // Thumb-2 supports both 16-bit and 32-bit instructions
1489        // 32-bit instructions are encoded as two 16-bit halfwords (big-endian order)
1490        match op {
1491            // === 16-bit Thumb encodings ===
1492            ArmOp::Add { rd, rn, op2 } => {
1493                let rd_bits = reg_to_bits(rd) as u16;
1494                let rn_bits = reg_to_bits(rn) as u16;
1495
1496                if let Operand2::Reg(rm) = op2 {
1497                    let rm_bits = reg_to_bits(rm) as u16;
1498                    // 16-bit ADDS only has 3-bit register fields (R0-R7). For
1499                    // high registers (e.g. R12, the MemLoad/MemStore base
1500                    // scratch) the bits overflow into adjacent fields, silently
1501                    // corrupting the operands — issue #178/#180: `add ip,ip,r0`
1502                    // was emitted as `adds r4,r5,r1`. Guard on all three regs
1503                    // being low and fall back to 32-bit ADD.W otherwise, exactly
1504                    // as the Sub handler below does.
1505                    if rd_bits < 8 && rn_bits < 8 && rm_bits < 8 {
1506                        // ADDS Rd, Rn, Rm (16-bit): 0001 100 Rm Rn Rd
1507                        let instr: u16 = 0x1800 | (rm_bits << 6) | (rn_bits << 3) | rd_bits;
1508                        Ok(instr.to_le_bytes().to_vec())
1509                    } else {
1510                        // ADD.W Rd, Rn, Rm (32-bit) for high registers
1511                        self.encode_thumb32_add_reg_raw(
1512                            rd_bits as u32,
1513                            rn_bits as u32,
1514                            rm_bits as u32,
1515                        )
1516                    }
1517                } else if let Operand2::Imm(imm) = op2 {
1518                    if *imm <= 7 && rd_bits < 8 && rn_bits < 8 {
1519                        // ADDS Rd, Rn, #imm3 (16-bit): 0001 110 imm3 Rn Rd
1520                        let instr: u16 = 0x1C00 | ((*imm as u16) << 6) | (rn_bits << 3) | rd_bits;
1521                        Ok(instr.to_le_bytes().to_vec())
1522                    } else {
1523                        // Use 32-bit ADD for larger immediates
1524                        self.encode_thumb32_add(rd, rn, *imm as u32)
1525                    }
1526                } else {
1527                    // Fallback to 32-bit encoding
1528                    self.encode_thumb32_add(rd, rn, 0)
1529                }
1530            }
1531
1532            ArmOp::Sub { rd, rn, op2 } => {
1533                let rd_bits = reg_to_bits(rd) as u16;
1534                let rn_bits = reg_to_bits(rn) as u16;
1535
1536                if let Operand2::Reg(rm) = op2 {
1537                    let rm_bits = reg_to_bits(rm) as u16;
1538                    // 16-bit SUBS can only use low registers (R0-R7)
1539                    if rd_bits < 8 && rn_bits < 8 && rm_bits < 8 {
1540                        // SUBS Rd, Rn, Rm (16-bit): 0001 101 Rm Rn Rd
1541                        let instr: u16 = 0x1A00 | (rm_bits << 6) | (rn_bits << 3) | rd_bits;
1542                        Ok(instr.to_le_bytes().to_vec())
1543                    } else {
1544                        // Use 32-bit SUB.W for high registers
1545                        self.encode_thumb32_sub_reg_raw(
1546                            rd_bits as u32,
1547                            rn_bits as u32,
1548                            rm_bits as u32,
1549                        )
1550                    }
1551                } else if let Operand2::Imm(imm) = op2 {
1552                    if *imm <= 7 && rd_bits < 8 && rn_bits < 8 {
1553                        // SUBS Rd, Rn, #imm3 (16-bit): 0001 111 imm3 Rn Rd
1554                        let instr: u16 = 0x1E00 | ((*imm as u16) << 6) | (rn_bits << 3) | rd_bits;
1555                        Ok(instr.to_le_bytes().to_vec())
1556                    } else {
1557                        self.encode_thumb32_sub(rd, rn, *imm as u32)
1558                    }
1559                } else {
1560                    self.encode_thumb32_sub(rd, rn, 0)
1561                }
1562            }
1563
1564            ArmOp::Mov { rd, op2 } => {
1565                let rd_bits = reg_to_bits(rd) as u16;
1566
1567                if let Operand2::Imm(imm) = op2 {
1568                    if *imm <= 255 && rd_bits < 8 {
1569                        // MOVS Rd, #imm8 (16-bit): 0010 0 Rd imm8
1570                        let imm_bits = (*imm as u16) & 0xFF;
1571                        let instr: u16 = 0x2000 | (rd_bits << 8) | imm_bits;
1572                        Ok(instr.to_le_bytes().to_vec())
1573                    } else {
1574                        // Use 32-bit MOVW for larger immediates
1575                        self.encode_thumb32_movw(rd, *imm as u32)
1576                    }
1577                } else if let Operand2::Reg(rm) = op2 {
1578                    let rm_bits = reg_to_bits(rm) as u16;
1579                    // MOV Rd, Rm (16-bit): 0100 0110 D Rm Rd[2:0]
1580                    // D = Rd[3], Rd[2:0] in lower bits
1581                    let d_bit = (rd_bits >> 3) & 1;
1582                    let instr: u16 = 0x4600 | (d_bit << 7) | (rm_bits << 3) | (rd_bits & 0x7);
1583                    Ok(instr.to_le_bytes().to_vec())
1584                } else {
1585                    let instr: u16 = 0xBF00; // NOP fallback
1586                    Ok(instr.to_le_bytes().to_vec())
1587                }
1588            }
1589
1590            ArmOp::Push { regs } => {
1591                // Thumb-2 PUSH encoding:
1592                // If all regs in R0-R7 + LR, use 16-bit: 1011 010 M rrrrrrrr
1593                // Otherwise use 32-bit: STMDB SP!, {regs} = 1110 1001 0010 1101 | 0M0 reglist(13)
1594                let mut reg_list: u16 = 0;
1595                let mut need_32bit = false;
1596                for r in regs {
1597                    let bit = reg_to_bits(r);
1598                    if bit >= 8 && *r != Reg::LR {
1599                        need_32bit = true;
1600                    }
1601                    reg_list |= 1 << bit;
1602                }
1603                if !need_32bit {
1604                    // 16-bit PUSH: 1011 010 M rrrrrrrr
1605                    let m_bit = if reg_list & (1 << 14) != 0 {
1606                        1u16
1607                    } else {
1608                        0u16
1609                    };
1610                    let low_regs = reg_list & 0xFF;
1611                    let instr: u16 = 0xB400 | (m_bit << 8) | low_regs;
1612                    Ok(instr.to_le_bytes().to_vec())
1613                } else {
1614                    // 32-bit STMDB SP!, {regs}: E92D | reglist(16)
1615                    let hw1: u16 = 0xE92D;
1616                    let hw2: u16 = reg_list;
1617                    let mut bytes = hw1.to_le_bytes().to_vec();
1618                    bytes.extend_from_slice(&hw2.to_le_bytes());
1619                    Ok(bytes)
1620                }
1621            }
1622
1623            ArmOp::Pop { regs } => {
1624                // Thumb-2 POP encoding:
1625                // If all regs in R0-R7 + PC, use 16-bit: 1011 110 P rrrrrrrr
1626                // Otherwise use 32-bit: LDMIA SP!, {regs} = 1110 1000 1011 1101 | PM0 reglist(13)
1627                let mut reg_list: u16 = 0;
1628                let mut need_32bit = false;
1629                for r in regs {
1630                    let bit = reg_to_bits(r);
1631                    if bit >= 8 && *r != Reg::PC {
1632                        need_32bit = true;
1633                    }
1634                    reg_list |= 1 << bit;
1635                }
1636                if !need_32bit {
1637                    // 16-bit POP: 1011 110 P rrrrrrrr
1638                    let p_bit = if reg_list & (1 << 15) != 0 {
1639                        1u16
1640                    } else {
1641                        0u16
1642                    };
1643                    let low_regs = reg_list & 0xFF;
1644                    let instr: u16 = 0xBC00 | (p_bit << 8) | low_regs;
1645                    Ok(instr.to_le_bytes().to_vec())
1646                } else {
1647                    // 32-bit LDMIA SP!, {regs}: E8BD | reglist(16)
1648                    let hw1: u16 = 0xE8BD;
1649                    let hw2: u16 = reg_list;
1650                    let mut bytes = hw1.to_le_bytes().to_vec();
1651                    bytes.extend_from_slice(&hw2.to_le_bytes());
1652                    Ok(bytes)
1653                }
1654            }
1655
1656            ArmOp::Nop => {
1657                let instr: u16 = 0xBF00; // NOP in Thumb-2
1658                Ok(instr.to_le_bytes().to_vec())
1659            }
1660
1661            ArmOp::Udf { imm } => {
1662                // UDF (Undefined) in Thumb-2: 16-bit encoding is 0xDE00 | imm8
1663                // This triggers UsageFault/HardFault, used for WASM traps
1664                let instr: u16 = 0xDE00 | (*imm as u16);
1665                let bytes = instr.to_le_bytes().to_vec();
1666                encoding_contracts::verify_thumb16(&bytes);
1667                Ok(bytes)
1668            }
1669
1670            // i64 support: ADDS, ADC, SUBS, SBC for register pair arithmetic
1671            // ADDS sets flags (carry), ADC uses carry from previous ADDS
1672            ArmOp::Adds { rd, rn, op2 } => {
1673                let rd_bits = reg_to_bits(rd) as u16;
1674                let rn_bits = reg_to_bits(rn) as u16;
1675
1676                if let Operand2::Reg(rm) = op2 {
1677                    let rm_bits = reg_to_bits(rm) as u16;
1678                    // 16-bit ADDS is R0-R7 only; i64 pair allocation can place
1679                    // operands in R8-R11, which would overflow the 3-bit fields
1680                    // and corrupt the operands (#178/#180 class). Guard and fall
1681                    // back to 32-bit ADDS.W for high registers.
1682                    if rd_bits < 8 && rn_bits < 8 && rm_bits < 8 {
1683                        // ADDS Rd, Rn, Rm (16-bit): 0001 100 Rm Rn Rd
1684                        let instr: u16 = 0x1800 | (rm_bits << 6) | (rn_bits << 3) | rd_bits;
1685                        Ok(instr.to_le_bytes().to_vec())
1686                    } else {
1687                        self.encode_thumb32_adds_reg_raw(
1688                            rd_bits as u32,
1689                            rn_bits as u32,
1690                            rm_bits as u32,
1691                        )
1692                    }
1693                } else {
1694                    // 32-bit Thumb-2 ADDS with immediate
1695                    self.encode_thumb32_adds(rd, rn, 0)
1696                }
1697            }
1698
1699            // ADC: Add with Carry (Thumb-2 32-bit)
1700            // ADC.W Rd, Rn, Rm: EB40 Rn | 00 Rd 00 Rm
1701            ArmOp::Adc { rd, rn, op2 } => {
1702                let rd_bits = reg_to_bits(rd);
1703                let rn_bits = reg_to_bits(rn);
1704
1705                if let Operand2::Reg(rm) = op2 {
1706                    let rm_bits = reg_to_bits(rm);
1707                    // ADC.W Rd, Rn, Rm (T2): 1110 1011 0100 Rn | 0 000 Rd 00 00 Rm
1708                    let hw1: u16 = (0xEB40 | rn_bits) as u16;
1709                    let hw2: u16 = ((rd_bits << 8) | rm_bits) as u16;
1710
1711                    let mut bytes = hw1.to_le_bytes().to_vec();
1712                    bytes.extend_from_slice(&hw2.to_le_bytes());
1713                    Ok(bytes)
1714                } else {
1715                    // ADC with immediate - use 32-bit encoding
1716                    let hw1: u16 = (0xF140 | rn_bits) as u16;
1717                    let hw2: u16 = (rd_bits << 8) as u16;
1718                    let mut bytes = hw1.to_le_bytes().to_vec();
1719                    bytes.extend_from_slice(&hw2.to_le_bytes());
1720                    Ok(bytes)
1721                }
1722            }
1723
1724            // SUBS sets flags (borrow), SBC uses borrow from previous SUBS
1725            ArmOp::Subs { rd, rn, op2 } => {
1726                let rd_bits = reg_to_bits(rd) as u16;
1727                let rn_bits = reg_to_bits(rn) as u16;
1728
1729                if let Operand2::Reg(rm) = op2 {
1730                    let rm_bits = reg_to_bits(rm) as u16;
1731                    // 16-bit SUBS is R0-R7 only; high-register i64 pair operands
1732                    // would overflow the 3-bit fields (#178/#180 class). Guard
1733                    // and fall back to 32-bit SUBS.W for high registers.
1734                    if rd_bits < 8 && rn_bits < 8 && rm_bits < 8 {
1735                        // SUBS Rd, Rn, Rm (16-bit): 0001 101 Rm Rn Rd
1736                        let instr: u16 = 0x1A00 | (rm_bits << 6) | (rn_bits << 3) | rd_bits;
1737                        Ok(instr.to_le_bytes().to_vec())
1738                    } else {
1739                        self.encode_thumb32_subs_reg_raw(
1740                            rd_bits as u32,
1741                            rn_bits as u32,
1742                            rm_bits as u32,
1743                        )
1744                    }
1745                } else {
1746                    // 32-bit Thumb-2 SUBS with immediate
1747                    self.encode_thumb32_subs(rd, rn, 0)
1748                }
1749            }
1750
1751            // SBC: Subtract with Carry (Thumb-2 32-bit)
1752            // SBC.W Rd, Rn, Rm: EB60 Rn | 00 Rd 00 Rm
1753            ArmOp::Sbc { rd, rn, op2 } => {
1754                let rd_bits = reg_to_bits(rd);
1755                let rn_bits = reg_to_bits(rn);
1756
1757                if let Operand2::Reg(rm) = op2 {
1758                    let rm_bits = reg_to_bits(rm);
1759                    // SBC.W Rd, Rn, Rm (T2): 1110 1011 0110 Rn | 0 000 Rd 00 00 Rm
1760                    let hw1: u16 = (0xEB60 | rn_bits) as u16;
1761                    let hw2: u16 = ((rd_bits << 8) | rm_bits) as u16;
1762
1763                    let mut bytes = hw1.to_le_bytes().to_vec();
1764                    bytes.extend_from_slice(&hw2.to_le_bytes());
1765                    Ok(bytes)
1766                } else {
1767                    // SBC with immediate - use 32-bit encoding
1768                    let hw1: u16 = (0xF160 | rn_bits) as u16;
1769                    let hw2: u16 = (rd_bits << 8) as u16;
1770                    let mut bytes = hw1.to_le_bytes().to_vec();
1771                    bytes.extend_from_slice(&hw2.to_le_bytes());
1772                    Ok(bytes)
1773                }
1774            }
1775
1776            // === 32-bit Thumb-2 encodings ===
1777
1778            // SDIV: 11111011 1001 Rn 1111 Rd 1111 Rm
1779            ArmOp::Sdiv { rd, rn, rm } => {
1780                let rd_bits = reg_to_bits(rd);
1781                let rn_bits = reg_to_bits(rn);
1782                let rm_bits = reg_to_bits(rm);
1783                reg_bits_checked(rd_bits)?;
1784                reg_bits_checked(rn_bits)?;
1785                reg_bits_checked(rm_bits)?;
1786
1787                // Thumb-2 SDIV: FB90 F0F0 | Rn<<16 | Rd<<8 | Rm
1788                // First halfword: 1111 1011 1001 Rn = 0xFB90 | Rn
1789                // Second halfword: 1111 Rd 1111 Rm = 0xF0F0 | Rd<<8 | Rm
1790                let hw1: u16 = (0xFB90 | rn_bits) as u16;
1791                let hw2: u16 = (0xF0F0 | (rd_bits << 8) | rm_bits) as u16;
1792
1793                // Thumb-2 32-bit instructions: first halfword, then second halfword (little-endian each)
1794                let mut bytes = hw1.to_le_bytes().to_vec();
1795                bytes.extend_from_slice(&hw2.to_le_bytes());
1796                encoding_contracts::verify_thumb32(&bytes);
1797                Ok(bytes)
1798            }
1799
1800            // UDIV: 11111011 1011 Rn 1111 Rd 1111 Rm
1801            ArmOp::Udiv { rd, rn, rm } => {
1802                let rd_bits = reg_to_bits(rd);
1803                let rn_bits = reg_to_bits(rn);
1804                let rm_bits = reg_to_bits(rm);
1805                reg_bits_checked(rd_bits)?;
1806                reg_bits_checked(rn_bits)?;
1807                reg_bits_checked(rm_bits)?;
1808
1809                // Thumb-2 UDIV: FBB0 F0F0 | Rn<<16 | Rd<<8 | Rm
1810                let hw1: u16 = (0xFBB0 | rn_bits) as u16;
1811                let hw2: u16 = (0xF0F0 | (rd_bits << 8) | rm_bits) as u16;
1812
1813                let mut bytes = hw1.to_le_bytes().to_vec();
1814                bytes.extend_from_slice(&hw2.to_le_bytes());
1815                encoding_contracts::verify_thumb32(&bytes);
1816                Ok(bytes)
1817            }
1818
1819            ArmOp::Umull { rdlo, rdhi, rn, rm } => {
1820                let rdlo_bits = reg_to_bits(rdlo);
1821                let rdhi_bits = reg_to_bits(rdhi);
1822                let rn_bits = reg_to_bits(rn);
1823                let rm_bits = reg_to_bits(rm);
1824                reg_bits_checked(rdlo_bits)?;
1825                reg_bits_checked(rdhi_bits)?;
1826                reg_bits_checked(rn_bits)?;
1827                reg_bits_checked(rm_bits)?;
1828
1829                // Thumb-2 UMULL: 1111 1011 1010 Rn | RdLo RdHi 0000 Rm
1830                let hw1: u16 = (0xFBA0 | rn_bits) as u16;
1831                let hw2: u16 = ((rdlo_bits << 12) | (rdhi_bits << 8) | rm_bits) as u16;
1832
1833                let mut bytes = hw1.to_le_bytes().to_vec();
1834                bytes.extend_from_slice(&hw2.to_le_bytes());
1835                encoding_contracts::verify_thumb32(&bytes);
1836                Ok(bytes)
1837            }
1838
1839            // MUL (Thumb-2 32-bit): MUL Rd, Rn, Rm
1840            ArmOp::Mul { rd, rn, rm } => {
1841                let rd_bits = reg_to_bits(rd);
1842                let rn_bits = reg_to_bits(rn);
1843                let rm_bits = reg_to_bits(rm);
1844
1845                // Thumb-2 MUL: FB00 F000 | Rn | Rd<<8 | Rm
1846                // 11111011 0000 Rn | 1111 Rd 0000 Rm
1847                let hw1: u16 = (0xFB00 | rn_bits) as u16;
1848                let hw2: u16 = (0xF000 | (rd_bits << 8) | rm_bits) as u16;
1849
1850                let mut bytes = hw1.to_le_bytes().to_vec();
1851                bytes.extend_from_slice(&hw2.to_le_bytes());
1852                Ok(bytes)
1853            }
1854
1855            // MLS: Rd = Ra - Rn * Rm
1856            ArmOp::Mls { rd, rn, rm, ra } => {
1857                let rd_bits = reg_to_bits(rd);
1858                let rn_bits = reg_to_bits(rn);
1859                let rm_bits = reg_to_bits(rm);
1860                let ra_bits = reg_to_bits(ra);
1861
1862                // Thumb-2 MLS: FB00 Rn | Ra Rd 0001 Rm
1863                // 11111011 0000 Rn | Ra Rd 0001 Rm
1864                let hw1: u16 = (0xFB00 | rn_bits) as u16;
1865                let hw2: u16 = ((ra_bits << 12) | (rd_bits << 8) | 0x10 | rm_bits) as u16;
1866
1867                let mut bytes = hw1.to_le_bytes().to_vec();
1868                bytes.extend_from_slice(&hw2.to_le_bytes());
1869                Ok(bytes)
1870            }
1871
1872            // AND (Thumb-2 32-bit)
1873            ArmOp::And { rd, rn, op2 } => {
1874                if let Operand2::Reg(rm) = op2 {
1875                    let rd_bits = reg_to_bits(rd);
1876                    let rn_bits = reg_to_bits(rn);
1877                    let rm_bits = reg_to_bits(rm);
1878
1879                    // Thumb-2 AND register: EA00 Rn | 0 Rd 00 00 Rm
1880                    let hw1: u16 = (0xEA00 | rn_bits) as u16;
1881                    let hw2: u16 = ((rd_bits << 8) | rm_bits) as u16;
1882
1883                    let mut bytes = hw1.to_le_bytes().to_vec();
1884                    bytes.extend_from_slice(&hw2.to_le_bytes());
1885                    Ok(bytes)
1886                } else if let Operand2::Imm(imm) = op2 {
1887                    let rd_bits = reg_to_bits(rd);
1888                    let rn_bits = reg_to_bits(rn);
1889                    let imm_val = *imm as u32;
1890
1891                    // Thumb-2 AND.W immediate T1: 11110 i 0 0000 S Rn | 0 imm3 Rd imm8
1892                    let i_bit = (imm_val >> 11) & 1;
1893                    let imm3 = (imm_val >> 8) & 0x7;
1894                    let imm8 = imm_val & 0xFF;
1895
1896                    let hw1: u16 = (0xF000 | (i_bit << 10) | rn_bits) as u16;
1897                    let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
1898
1899                    let mut bytes = hw1.to_le_bytes().to_vec();
1900                    bytes.extend_from_slice(&hw2.to_le_bytes());
1901                    Ok(bytes)
1902                } else {
1903                    // RegShift variant - fallback to NOP
1904                    let instr: u16 = 0xBF00;
1905                    Ok(instr.to_le_bytes().to_vec())
1906                }
1907            }
1908
1909            // ORR (Thumb-2 32-bit)
1910            ArmOp::Orr { rd, rn, op2 } => {
1911                if let Operand2::Reg(rm) = op2 {
1912                    let rd_bits = reg_to_bits(rd);
1913                    let rn_bits = reg_to_bits(rn);
1914                    let rm_bits = reg_to_bits(rm);
1915
1916                    // Thumb-2 ORR: EA40 Rn | 0 Rd 00 00 Rm
1917                    let hw1: u16 = (0xEA40 | rn_bits) as u16;
1918                    let hw2: u16 = ((rd_bits << 8) | rm_bits) as u16;
1919
1920                    let mut bytes = hw1.to_le_bytes().to_vec();
1921                    bytes.extend_from_slice(&hw2.to_le_bytes());
1922                    Ok(bytes)
1923                } else if let Operand2::Imm(imm) = op2 {
1924                    // ORR.W immediate T1: 11110 i 0 0010 S Rn | 0 imm3 Rd imm8.
1925                    // Only the zero-extended byte form (imm <= 0xFF) is encoded;
1926                    // larger modified immediates need ThumbExpandImm — return an
1927                    // error rather than silently emit a NOP (Ok-or-Err, #180/#185).
1928                    let imm_val = *imm as u32;
1929                    if imm_val > 0xFF {
1930                        return Err(synth_core::Error::synthesis(
1931                            "ORR immediate > 0xFF requires ThumbExpandImm (not yet implemented)",
1932                        ));
1933                    }
1934                    let rd_bits = reg_to_bits(rd);
1935                    let rn_bits = reg_to_bits(rn);
1936                    let hw1: u16 = (0xF040 | rn_bits) as u16;
1937                    let hw2: u16 = ((rd_bits << 8) | (imm_val & 0xFF)) as u16;
1938                    let mut bytes = hw1.to_le_bytes().to_vec();
1939                    bytes.extend_from_slice(&hw2.to_le_bytes());
1940                    Ok(bytes)
1941                } else {
1942                    let instr: u16 = 0xBF00;
1943                    Ok(instr.to_le_bytes().to_vec())
1944                }
1945            }
1946
1947            // EOR (Thumb-2 32-bit)
1948            ArmOp::Eor { rd, rn, op2 } => {
1949                if let Operand2::Reg(rm) = op2 {
1950                    let rd_bits = reg_to_bits(rd);
1951                    let rn_bits = reg_to_bits(rn);
1952                    let rm_bits = reg_to_bits(rm);
1953
1954                    // Thumb-2 EOR: EA80 Rn | 0 Rd 00 00 Rm
1955                    let hw1: u16 = (0xEA80 | rn_bits) as u16;
1956                    let hw2: u16 = ((rd_bits << 8) | rm_bits) as u16;
1957
1958                    let mut bytes = hw1.to_le_bytes().to_vec();
1959                    bytes.extend_from_slice(&hw2.to_le_bytes());
1960                    Ok(bytes)
1961                } else if let Operand2::Imm(imm) = op2 {
1962                    // EOR.W immediate T1: 11110 i 0 0100 S Rn | 0 imm3 Rd imm8.
1963                    // Byte form only (imm <= 0xFF); larger needs ThumbExpandImm —
1964                    // error, not a silent NOP (Ok-or-Err, #180/#185).
1965                    let imm_val = *imm as u32;
1966                    if imm_val > 0xFF {
1967                        return Err(synth_core::Error::synthesis(
1968                            "EOR immediate > 0xFF requires ThumbExpandImm (not yet implemented)",
1969                        ));
1970                    }
1971                    let rd_bits = reg_to_bits(rd);
1972                    let rn_bits = reg_to_bits(rn);
1973                    let hw1: u16 = (0xF080 | rn_bits) as u16;
1974                    let hw2: u16 = ((rd_bits << 8) | (imm_val & 0xFF)) as u16;
1975                    let mut bytes = hw1.to_le_bytes().to_vec();
1976                    bytes.extend_from_slice(&hw2.to_le_bytes());
1977                    Ok(bytes)
1978                } else {
1979                    let instr: u16 = 0xBF00;
1980                    Ok(instr.to_le_bytes().to_vec())
1981                }
1982            }
1983
1984            // Shift operations (16-bit for low registers)
1985            ArmOp::Lsl { rd, rn, shift } => {
1986                let rd_bits = reg_to_bits(rd) as u16;
1987                let rn_bits = reg_to_bits(rn) as u16;
1988                let shift_bits = (*shift as u16) & 0x1F;
1989
1990                if rd_bits < 8 && rn_bits < 8 {
1991                    // LSLS Rd, Rm, #imm5 (16-bit): 0000 0 imm5 Rm Rd
1992                    let instr: u16 = (shift_bits << 6) | (rn_bits << 3) | rd_bits;
1993                    Ok(instr.to_le_bytes().to_vec())
1994                } else {
1995                    // Use 32-bit encoding for high registers
1996                    self.encode_thumb32_shift(rd, rn, *shift, 0b00) // LSL type
1997                }
1998            }
1999
2000            ArmOp::Lsr { rd, rn, shift } => {
2001                let rd_bits = reg_to_bits(rd) as u16;
2002                let rn_bits = reg_to_bits(rn) as u16;
2003                let shift_bits = (*shift as u16) & 0x1F;
2004
2005                if rd_bits < 8 && rn_bits < 8 && shift_bits > 0 {
2006                    // LSRS Rd, Rm, #imm5 (16-bit): 0000 1 imm5 Rm Rd
2007                    let instr: u16 = 0x0800 | (shift_bits << 6) | (rn_bits << 3) | rd_bits;
2008                    Ok(instr.to_le_bytes().to_vec())
2009                } else {
2010                    self.encode_thumb32_shift(rd, rn, *shift, 0b01) // LSR type
2011                }
2012            }
2013
2014            ArmOp::Asr { rd, rn, shift } => {
2015                let rd_bits = reg_to_bits(rd) as u16;
2016                let rn_bits = reg_to_bits(rn) as u16;
2017                let shift_bits = (*shift as u16) & 0x1F;
2018
2019                if rd_bits < 8 && rn_bits < 8 && shift_bits > 0 {
2020                    // ASRS Rd, Rm, #imm5 (16-bit): 0001 0 imm5 Rm Rd
2021                    let instr: u16 = 0x1000 | (shift_bits << 6) | (rn_bits << 3) | rd_bits;
2022                    Ok(instr.to_le_bytes().to_vec())
2023                } else {
2024                    self.encode_thumb32_shift(rd, rn, *shift, 0b10) // ASR type
2025                }
2026            }
2027
2028            ArmOp::Ror { rd, rn, shift } => {
2029                // ROR doesn't have a 16-bit immediate form, use 32-bit
2030                self.encode_thumb32_shift(rd, rn, *shift, 0b11) // ROR type
2031            }
2032
2033            // Register-based shifts (Thumb-2 32-bit)
2034            // Encoding: 11111010 0xxS Rn 1111 Rd 0000 Rm
2035            // xx = shift type: 00=LSL, 01=LSR, 10=ASR, 11=ROR
2036            ArmOp::LslReg { rd, rn, rm } => self.encode_thumb32_shift_reg(rd, rn, rm, 0b00),
2037            ArmOp::LsrReg { rd, rn, rm } => self.encode_thumb32_shift_reg(rd, rn, rm, 0b01),
2038            ArmOp::AsrReg { rd, rn, rm } => self.encode_thumb32_shift_reg(rd, rn, rm, 0b10),
2039            ArmOp::RorReg { rd, rn, rm } => self.encode_thumb32_shift_reg(rd, rn, rm, 0b11),
2040
2041            // RSB (Reverse Subtract): Rd = imm - Rn
2042            // Thumb-2 T2 encoding: 11110 i 0 1110 S Rn | 0 imm3 Rd imm8
2043            ArmOp::Rsb { rd, rn, imm } => {
2044                let rd_bits = reg_to_bits(rd);
2045                let rn_bits = reg_to_bits(rn);
2046                let imm_val = *imm;
2047
2048                let i_bit = (imm_val >> 11) & 1;
2049                let imm3 = (imm_val >> 8) & 0x7;
2050                let imm8 = imm_val & 0xFF;
2051
2052                // hw1: 11110 i 01110 0 Rn  (S=0)
2053                let hw1: u16 = (0xF1C0 | (i_bit << 10) | rn_bits) as u16;
2054                // hw2: 0 imm3 Rd imm8
2055                let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
2056
2057                let mut bytes = hw1.to_le_bytes().to_vec();
2058                bytes.extend_from_slice(&hw2.to_le_bytes());
2059                Ok(bytes)
2060            }
2061
2062            // CLZ (Thumb-2 32-bit)
2063            ArmOp::Clz { rd, rm } => {
2064                let rd_bits = reg_to_bits(rd);
2065                let rm_bits = reg_to_bits(rm);
2066
2067                // Thumb-2 CLZ: FAB0 Rm | F8 Rd Rm
2068                // 11111010 1011 Rm | 1111 1000 Rd Rm
2069                let hw1: u16 = (0xFAB0 | rm_bits) as u16;
2070                let hw2: u16 = (0xF080 | (rd_bits << 8) | rm_bits) as u16;
2071
2072                let mut bytes = hw1.to_le_bytes().to_vec();
2073                bytes.extend_from_slice(&hw2.to_le_bytes());
2074                Ok(bytes)
2075            }
2076
2077            // RBIT (Thumb-2 32-bit)
2078            ArmOp::Rbit { rd, rm } => {
2079                let rd_bits = reg_to_bits(rd);
2080                let rm_bits = reg_to_bits(rm);
2081
2082                // Thumb-2 RBIT: FA90 Rm | F0 Rd A0 Rm
2083                // 11111010 1001 Rm | 1111 Rd 1010 Rm
2084                let hw1: u16 = (0xFA90 | rm_bits) as u16;
2085                let hw2: u16 = (0xF0A0 | (rd_bits << 8) | rm_bits) as u16;
2086
2087                let mut bytes = hw1.to_le_bytes().to_vec();
2088                bytes.extend_from_slice(&hw2.to_le_bytes());
2089                Ok(bytes)
2090            }
2091
2092            // SXTB (16-bit for low registers)
2093            ArmOp::Sxtb { rd, rm } => {
2094                let rd_bits = reg_to_bits(rd) as u16;
2095                let rm_bits = reg_to_bits(rm) as u16;
2096
2097                if rd_bits < 8 && rm_bits < 8 {
2098                    // SXTB Rd, Rm (16-bit): 1011 0010 01 Rm Rd
2099                    let instr: u16 = 0xB240 | (rm_bits << 3) | rd_bits;
2100                    Ok(instr.to_le_bytes().to_vec())
2101                } else {
2102                    // Thumb-2 SXTB.W: FA4F F(rd)80 (rm)
2103                    // 11111010 0100 1111 | 1111 Rd 10 rotate Rm
2104                    let rd_bits32 = rd_bits as u32;
2105                    let rm_bits32 = rm_bits as u32;
2106                    let hw1: u16 = 0xFA4F;
2107                    let hw2: u16 = (0xF080 | (rd_bits32 << 8) | rm_bits32) as u16;
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
2114            // SXTH (16-bit for low registers)
2115            ArmOp::Sxth { rd, rm } => {
2116                let rd_bits = reg_to_bits(rd) as u16;
2117                let rm_bits = reg_to_bits(rm) as u16;
2118
2119                if rd_bits < 8 && rm_bits < 8 {
2120                    // SXTH Rd, Rm (16-bit): 1011 0010 00 Rm Rd
2121                    let instr: u16 = 0xB200 | (rm_bits << 3) | rd_bits;
2122                    Ok(instr.to_le_bytes().to_vec())
2123                } else {
2124                    // Thumb-2 SXTH.W: FA0F F(rd)80 (rm)
2125                    // 11111010 0000 1111 | 1111 Rd 10 rotate Rm
2126                    let rd_bits32 = rd_bits as u32;
2127                    let rm_bits32 = rm_bits as u32;
2128                    let hw1: u16 = 0xFA0F;
2129                    let hw2: u16 = (0xF080 | (rd_bits32 << 8) | rm_bits32) as u16;
2130                    let mut bytes = hw1.to_le_bytes().to_vec();
2131                    bytes.extend_from_slice(&hw2.to_le_bytes());
2132                    Ok(bytes)
2133                }
2134            }
2135
2136            // CMP (can be 16-bit for low registers)
2137            ArmOp::Cmp { rn, op2 } => {
2138                let rn_bits = reg_to_bits(rn) as u16;
2139
2140                if let Operand2::Imm(imm) = op2 {
2141                    // Only use 16-bit encoding for non-negative immediates 0-255
2142                    // Negative immediates must use 32-bit encoding
2143                    if *imm >= 0 && *imm <= 255 && rn_bits < 8 {
2144                        // CMP Rn, #imm8 (16-bit): 0010 1 Rn imm8
2145                        let instr: u16 = 0x2800 | (rn_bits << 8) | (*imm as u16 & 0xFF);
2146                        Ok(instr.to_le_bytes().to_vec())
2147                    } else {
2148                        self.encode_thumb32_cmp_imm(rn, *imm as u32)
2149                    }
2150                } else if let Operand2::Reg(rm) = op2 {
2151                    let rm_bits = reg_to_bits(rm) as u16;
2152                    if rn_bits < 8 && rm_bits < 8 {
2153                        // CMP Rn, Rm (16-bit low): 0100 0010 10 Rm Rn
2154                        let instr: u16 = 0x4280 | (rm_bits << 3) | rn_bits;
2155                        Ok(instr.to_le_bytes().to_vec())
2156                    } else {
2157                        // CMP Rn, Rm (16-bit high): 0100 0101 N Rm Rn[2:0]
2158                        let n_bit = (rn_bits >> 3) & 1;
2159                        let instr: u16 = 0x4500 | (n_bit << 7) | (rm_bits << 3) | (rn_bits & 0x7);
2160                        Ok(instr.to_le_bytes().to_vec())
2161                    }
2162                } else {
2163                    let instr: u16 = 0xBF00;
2164                    Ok(instr.to_le_bytes().to_vec())
2165                }
2166            }
2167
2168            // CMN (Compare Negative) - computes Rn + op2 and sets flags
2169            // CMN Rn, #1 sets Z flag if Rn == -1 (since -1 + 1 = 0)
2170            ArmOp::Cmn { rn, op2 } => {
2171                let rn_bits = reg_to_bits(rn) as u16;
2172
2173                if let Operand2::Imm(imm) = op2 {
2174                    // CMN.W Rn, #imm (32-bit encoding)
2175                    // Encoding: F110 Rn | 0F00 imm8 (for small immediates 0-255)
2176                    if *imm >= 0 && *imm <= 255 {
2177                        let imm8 = *imm as u16 & 0xFF;
2178                        let hw1: u16 = 0xF110 | rn_bits;
2179                        let hw2: u16 = 0x0F00 | imm8;
2180                        let mut bytes = hw1.to_le_bytes().to_vec();
2181                        bytes.extend_from_slice(&hw2.to_le_bytes());
2182                        Ok(bytes)
2183                    } else {
2184                        // For other immediates, fallback to NOP (should not happen in our use case)
2185                        Ok(vec![0xBF, 0x00])
2186                    }
2187                } else if let Operand2::Reg(rm) = op2 {
2188                    let rm_bits = reg_to_bits(rm) as u16;
2189                    // 16-bit CMN (T1) only encodes R0-R7; high registers overflow
2190                    // the 3-bit fields and corrupt the operands (#184, the #180
2191                    // class). CMN has no high-register 16-bit form, so fall back
2192                    // to 32-bit CMN.W (T2): EB10 Rn | 0F00 Rm (ADD.W with S=1 and
2193                    // Rd discarded as PC/1111).
2194                    if rn_bits < 8 && rm_bits < 8 {
2195                        // CMN Rn, Rm (16-bit): 0100 0010 11 Rm Rn
2196                        let instr: u16 = 0x42C0 | (rm_bits << 3) | rn_bits;
2197                        Ok(instr.to_le_bytes().to_vec())
2198                    } else {
2199                        let hw1: u16 = 0xEB10 | rn_bits;
2200                        let hw2: u16 = 0x0F00 | rm_bits;
2201                        let mut bytes = hw1.to_le_bytes().to_vec();
2202                        bytes.extend_from_slice(&hw2.to_le_bytes());
2203                        Ok(bytes)
2204                    }
2205                } else {
2206                    Ok(vec![0xBF, 0x00])
2207                }
2208            }
2209
2210            // LDR (can be 16-bit for simple cases)
2211            ArmOp::Ldr { rd, addr } => {
2212                let rd_bits = reg_to_bits(rd);
2213                let base_bits = reg_to_bits(&addr.base);
2214
2215                // Handle register offset mode [base, Roff] or [base, Roff, #imm]
2216                if let Some(offset_reg) = &addr.offset_reg {
2217                    let rm_bits = reg_to_bits(offset_reg);
2218
2219                    // If there's also an immediate offset, we need to ADD it first
2220                    if addr.offset != 0 {
2221                        // Use R12 (IP) as scratch to avoid clobbering the address register
2222                        // ADD R12, Rm, #offset; LDR Rd, [base, R12]
2223                        let scratch = Reg::R12;
2224                        let mut bytes =
2225                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2226                        bytes.extend(self.encode_thumb32_ldr_reg(rd, &addr.base, &scratch)?);
2227                        return Ok(bytes);
2228                    }
2229
2230                    // Simple register offset: LDR Rd, [Rn, Rm]
2231                    // 16-bit: only if Rd, Rn, Rm < R8
2232                    if rd_bits < 8 && base_bits < 8 && rm_bits < 8 {
2233                        // LDR Rd, [Rn, Rm] (16-bit): 0101 100 Rm Rn Rd
2234                        let instr: u16 = 0x5800
2235                            | ((rm_bits as u16) << 6)
2236                            | ((base_bits as u16) << 3)
2237                            | (rd_bits as u16);
2238                        return Ok(instr.to_le_bytes().to_vec());
2239                    }
2240
2241                    // 32-bit register offset
2242                    return self.encode_thumb32_ldr_reg(rd, &addr.base, offset_reg);
2243                }
2244
2245                // Immediate offset mode [base, #imm]
2246                let offset = addr.offset as u32;
2247
2248                if rd_bits < 8 && base_bits < 8 && (offset & 0x3) == 0 && offset <= 124 {
2249                    // LDR Rd, [Rn, #imm5*4] (16-bit): 0110 1 imm5 Rn Rd
2250                    let imm5 = (offset >> 2) as u16;
2251                    let instr: u16 =
2252                        0x6800 | (imm5 << 6) | ((base_bits as u16) << 3) | (rd_bits as u16);
2253                    Ok(instr.to_le_bytes().to_vec())
2254                } else {
2255                    self.encode_thumb32_ldr(rd, &addr.base, offset)
2256                }
2257            }
2258
2259            // STR (can be 16-bit for simple cases)
2260            ArmOp::Str { rd, addr } => {
2261                let rd_bits = reg_to_bits(rd);
2262                let base_bits = reg_to_bits(&addr.base);
2263
2264                // Handle register offset mode [base, Roff] or [base, Roff, #imm]
2265                if let Some(offset_reg) = &addr.offset_reg {
2266                    let rm_bits = reg_to_bits(offset_reg);
2267
2268                    // If there's also an immediate offset, we need to ADD it first
2269                    if addr.offset != 0 {
2270                        // Use R12 (IP) as scratch to avoid clobbering the address register
2271                        // ADD R12, Rm, #offset; STR Rd, [base, R12]
2272                        let scratch = Reg::R12;
2273                        let mut bytes =
2274                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2275                        bytes.extend(self.encode_thumb32_str_reg(rd, &addr.base, &scratch)?);
2276                        return Ok(bytes);
2277                    }
2278
2279                    // Simple register offset: STR Rd, [Rn, Rm]
2280                    // 16-bit: only if Rd, Rn, Rm < R8
2281                    if rd_bits < 8 && base_bits < 8 && rm_bits < 8 {
2282                        // STR Rd, [Rn, Rm] (16-bit): 0101 000 Rm Rn Rd
2283                        let instr: u16 = 0x5000
2284                            | ((rm_bits as u16) << 6)
2285                            | ((base_bits as u16) << 3)
2286                            | (rd_bits as u16);
2287                        return Ok(instr.to_le_bytes().to_vec());
2288                    }
2289
2290                    // 32-bit register offset
2291                    return self.encode_thumb32_str_reg(rd, &addr.base, offset_reg);
2292                }
2293
2294                // Immediate offset mode [base, #imm]
2295                let offset = addr.offset as u32;
2296
2297                if rd_bits < 8 && base_bits < 8 && (offset & 0x3) == 0 && offset <= 124 {
2298                    // STR Rd, [Rn, #imm5*4] (16-bit): 0110 0 imm5 Rn Rd
2299                    let imm5 = (offset >> 2) as u16;
2300                    let instr: u16 =
2301                        0x6000 | (imm5 << 6) | ((base_bits as u16) << 3) | (rd_bits as u16);
2302                    Ok(instr.to_le_bytes().to_vec())
2303                } else {
2304                    self.encode_thumb32_str(rd, &addr.base, offset)
2305                }
2306            }
2307
2308            // LDRB (Thumb-2)
2309            ArmOp::Ldrb { rd, addr } => {
2310                let rd_bits = reg_to_bits(rd);
2311                let base_bits = reg_to_bits(&addr.base);
2312
2313                if let Some(offset_reg) = &addr.offset_reg {
2314                    if addr.offset != 0 {
2315                        let scratch = Reg::R12;
2316                        let mut bytes =
2317                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2318                        bytes.extend(self.encode_thumb32_ldrb_reg(rd, &addr.base, &scratch)?);
2319                        return Ok(bytes);
2320                    }
2321                    return self.encode_thumb32_ldrb_reg(rd, &addr.base, offset_reg);
2322                }
2323
2324                let offset = addr.offset as u32;
2325                if rd_bits < 8 && base_bits < 8 && offset <= 31 {
2326                    // LDRB Rd, [Rn, #imm5] (16-bit): 0111 1 imm5 Rn Rd
2327                    let instr: u16 = 0x7800
2328                        | ((offset as u16) << 6)
2329                        | ((base_bits as u16) << 3)
2330                        | (rd_bits as u16);
2331                    Ok(instr.to_le_bytes().to_vec())
2332                } else {
2333                    self.encode_thumb32_ldrb_imm(rd, &addr.base, offset)
2334                }
2335            }
2336
2337            // LDRSB (Thumb-2)
2338            ArmOp::Ldrsb { rd, addr } => {
2339                let rd_bits = reg_to_bits(rd);
2340                let base_bits = reg_to_bits(&addr.base);
2341
2342                if let Some(offset_reg) = &addr.offset_reg {
2343                    if addr.offset != 0 {
2344                        let scratch = Reg::R12;
2345                        let mut bytes =
2346                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2347                        bytes.extend(self.encode_thumb32_ldrsb_reg(rd, &addr.base, &scratch)?);
2348                        return Ok(bytes);
2349                    }
2350                    return self.encode_thumb32_ldrsb_reg(rd, &addr.base, offset_reg);
2351                }
2352
2353                let offset = addr.offset as u32;
2354                // LDRSB has no 16-bit immediate form (only register)
2355                // For 16-bit reg form: only if Rd, Rn, Rm < R8
2356                if rd_bits < 8 && base_bits < 8 && offset == 0 {
2357                    // No immediate 16-bit encoding for LDRSB; use 32-bit
2358                    self.encode_thumb32_ldrsb_imm(rd, &addr.base, offset)
2359                } else {
2360                    self.encode_thumb32_ldrsb_imm(rd, &addr.base, offset)
2361                }
2362            }
2363
2364            // LDRH (Thumb-2)
2365            ArmOp::Ldrh { rd, addr } => {
2366                let rd_bits = reg_to_bits(rd);
2367                let base_bits = reg_to_bits(&addr.base);
2368
2369                if let Some(offset_reg) = &addr.offset_reg {
2370                    if addr.offset != 0 {
2371                        let scratch = Reg::R12;
2372                        let mut bytes =
2373                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2374                        bytes.extend(self.encode_thumb32_ldrh_reg(rd, &addr.base, &scratch)?);
2375                        return Ok(bytes);
2376                    }
2377                    return self.encode_thumb32_ldrh_reg(rd, &addr.base, offset_reg);
2378                }
2379
2380                let offset = addr.offset as u32;
2381                if rd_bits < 8 && base_bits < 8 && (offset & 0x1) == 0 && offset <= 62 {
2382                    // LDRH Rd, [Rn, #imm5*2] (16-bit): 1000 1 imm5 Rn Rd
2383                    let imm5 = (offset >> 1) as u16;
2384                    let instr: u16 =
2385                        0x8800 | (imm5 << 6) | ((base_bits as u16) << 3) | (rd_bits as u16);
2386                    Ok(instr.to_le_bytes().to_vec())
2387                } else {
2388                    self.encode_thumb32_ldrh_imm(rd, &addr.base, offset)
2389                }
2390            }
2391
2392            // LDRSH (Thumb-2)
2393            ArmOp::Ldrsh { rd, addr } => {
2394                if let Some(offset_reg) = &addr.offset_reg {
2395                    if addr.offset != 0 {
2396                        let scratch = Reg::R12;
2397                        let mut bytes =
2398                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2399                        bytes.extend(self.encode_thumb32_ldrsh_reg(rd, &addr.base, &scratch)?);
2400                        return Ok(bytes);
2401                    }
2402                    return self.encode_thumb32_ldrsh_reg(rd, &addr.base, offset_reg);
2403                }
2404
2405                let offset = addr.offset as u32;
2406                self.encode_thumb32_ldrsh_imm(rd, &addr.base, offset)
2407            }
2408
2409            // STRB (Thumb-2)
2410            ArmOp::Strb { rd, addr } => {
2411                let rd_bits = reg_to_bits(rd);
2412                let base_bits = reg_to_bits(&addr.base);
2413
2414                if let Some(offset_reg) = &addr.offset_reg {
2415                    if addr.offset != 0 {
2416                        let scratch = Reg::R12;
2417                        let mut bytes =
2418                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2419                        bytes.extend(self.encode_thumb32_strb_reg(rd, &addr.base, &scratch)?);
2420                        return Ok(bytes);
2421                    }
2422                    return self.encode_thumb32_strb_reg(rd, &addr.base, offset_reg);
2423                }
2424
2425                let offset = addr.offset as u32;
2426                if rd_bits < 8 && base_bits < 8 && offset <= 31 {
2427                    // STRB Rd, [Rn, #imm5] (16-bit): 0111 0 imm5 Rn Rd
2428                    let instr: u16 = 0x7000
2429                        | ((offset as u16) << 6)
2430                        | ((base_bits as u16) << 3)
2431                        | (rd_bits as u16);
2432                    Ok(instr.to_le_bytes().to_vec())
2433                } else {
2434                    self.encode_thumb32_strb_imm(rd, &addr.base, offset)
2435                }
2436            }
2437
2438            // STRH (Thumb-2)
2439            ArmOp::Strh { rd, addr } => {
2440                let rd_bits = reg_to_bits(rd);
2441                let base_bits = reg_to_bits(&addr.base);
2442
2443                if let Some(offset_reg) = &addr.offset_reg {
2444                    if addr.offset != 0 {
2445                        let scratch = Reg::R12;
2446                        let mut bytes =
2447                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2448                        bytes.extend(self.encode_thumb32_strh_reg(rd, &addr.base, &scratch)?);
2449                        return Ok(bytes);
2450                    }
2451                    return self.encode_thumb32_strh_reg(rd, &addr.base, offset_reg);
2452                }
2453
2454                let offset = addr.offset as u32;
2455                if rd_bits < 8 && base_bits < 8 && (offset & 0x1) == 0 && offset <= 62 {
2456                    // STRH Rd, [Rn, #imm5*2] (16-bit): 1000 0 imm5 Rn Rd
2457                    let imm5 = (offset >> 1) as u16;
2458                    let instr: u16 =
2459                        0x8000 | (imm5 << 6) | ((base_bits as u16) << 3) | (rd_bits as u16);
2460                    Ok(instr.to_le_bytes().to_vec())
2461                } else {
2462                    self.encode_thumb32_strh_imm(rd, &addr.base, offset)
2463                }
2464            }
2465
2466            // MemorySize (Thumb-2)
2467            ArmOp::MemorySize { rd } => {
2468                // LSR rd, R10, #16 — memory size in bytes / 65536 = pages
2469                // Thumb-2 16-bit: LSRS Rd, Rm, #imm5 — 0000 1 imm5 Rm Rd
2470                let rd_bits = reg_to_bits(rd);
2471                let r10_bits = reg_to_bits(&Reg::R10);
2472                if rd_bits < 8 && r10_bits < 8 {
2473                    let instr: u16 =
2474                        0x0800 | (16u16 << 6) | ((r10_bits as u16) << 3) | (rd_bits as u16);
2475                    Ok(instr.to_le_bytes().to_vec())
2476                } else {
2477                    // Thumb-2 32-bit LSR: 1110 1010 010 0 1111 | 0 imm3 Rd imm2 01 Rm
2478                    let imm5: u32 = 16;
2479                    let imm3 = (imm5 >> 2) & 0x7;
2480                    let imm2 = imm5 & 0x3;
2481                    let hw1: u16 = 0xEA4F;
2482                    let hw2: u16 =
2483                        ((imm3 << 12) | (rd_bits << 8) | (imm2 << 6) | 0x10 | r10_bits) as u16;
2484                    let mut bytes = hw1.to_le_bytes().to_vec();
2485                    bytes.extend_from_slice(&hw2.to_le_bytes());
2486                    Ok(bytes)
2487                }
2488            }
2489
2490            // MemoryGrow (Thumb-2)
2491            ArmOp::MemoryGrow { rd, .. } => {
2492                // On embedded with fixed memory, always return -1 (failure)
2493                // MVN rd, #0 → MOV rd, #-1
2494                // Thumb-2 32-bit: MVN: 1111 0 i 0 0 0 1 1 0 1111 | 0 imm3 Rd imm8
2495                let rd_bits = reg_to_bits(rd);
2496                let hw1: u16 = 0xF06F; // MVN with i=0
2497                let hw2: u16 = (rd_bits << 8) as u16; // imm8=0 → ~0 = 0xFFFFFFFF = -1
2498                let mut bytes = hw1.to_le_bytes().to_vec();
2499                bytes.extend_from_slice(&hw2.to_le_bytes());
2500                Ok(bytes)
2501            }
2502
2503            // BX (16-bit)
2504            ArmOp::Bx { rm } => {
2505                let rm_bits = reg_to_bits(rm) as u16;
2506                // BX Rm (16-bit): 0100 0111 0 Rm 000
2507                let instr: u16 = 0x4700 | (rm_bits << 3);
2508                Ok(instr.to_le_bytes().to_vec())
2509            }
2510
2511            // BLX (16-bit) - Branch with Link and Exchange
2512            // BLX Rm: 0100 0111 1 Rm 000
2513            ArmOp::Blx { rm } => {
2514                let rm_bits = reg_to_bits(rm) as u16;
2515                let instr: u16 = 0x4780 | (rm_bits << 3);
2516                Ok(instr.to_le_bytes().to_vec())
2517            }
2518
2519            // CallIndirect - indirect function call via table lookup
2520            // table_index_reg contains the table index
2521            // Generates: LSL R12, idx, #2; LDR R12, [R12, table_base]; BLX R12
2522            ArmOp::CallIndirect {
2523                rd: _,
2524                type_idx: _,
2525                table_index_reg,
2526            } => {
2527                let idx_reg = reg_to_bits(table_index_reg);
2528                let mut bytes = Vec::new();
2529
2530                // For now, we generate code that:
2531                // 1. Multiplies index by 4 (function pointer size)
2532                // 2. Loads function pointer from table (assumes table base in R11)
2533                // 3. Calls the function via BLX
2534                //
2535                // Table base setup must be done by caller/runtime.
2536                // This is a simplified implementation - full support needs:
2537                // - Table base address resolution
2538                // - Type signature checking
2539                // - Bounds checking
2540
2541                // LSL R12, idx_reg, #2 (multiply index by 4)
2542                // Thumb-2 MOV with shift: 11101010 010 S 1111 | 0 imm3 Rd imm2 type Rm
2543                // LSL: type=00, imm5=2 -> imm3=0, imm2=10
2544                let hw1: u16 = 0xEA4F_u16; // MOV.W R12, Rm, LSL #2
2545                let hw2: u16 = ((0x0C00 | (0b10 << 4)) | idx_reg) as u16;
2546                bytes.extend_from_slice(&hw1.to_le_bytes());
2547                bytes.extend_from_slice(&hw2.to_le_bytes());
2548
2549                // LDR R12, [R11, R12] - load function pointer
2550                // Thumb-2 LDR (register): 1111 1000 0101 Rn | Rt 0000 00 imm2 Rm
2551                // Rn=R11, Rt=R12, Rm=R12, imm2=00 (no shift)
2552                let ldr_hw1: u16 = 0xF85B; // LDR.W Rt, [R11, Rm]
2553                let ldr_hw2: u16 = 0xC00C; // Rt=R12, imm2=00, Rm=R12
2554                bytes.extend_from_slice(&ldr_hw1.to_le_bytes());
2555                bytes.extend_from_slice(&ldr_hw2.to_le_bytes());
2556
2557                // BLX R12 (call function indirectly)
2558                // BLX Rm (16-bit): 0100 0111 1 Rm 000
2559                let blx: u16 = 0x47E0; // BLX R12
2560                bytes.extend_from_slice(&blx.to_le_bytes());
2561
2562                Ok(bytes)
2563            }
2564
2565            // Label pseudo-instruction: emits no machine code
2566            ArmOp::Label { .. } => Ok(Vec::new()),
2567
2568            // Conditional branch to label (generic) - offset 0, will be patched
2569            ArmOp::Bcc { cond, label: _ } => {
2570                use synth_synthesis::Condition;
2571                let cond_bits: u16 = match cond {
2572                    Condition::EQ => 0x0,
2573                    Condition::NE => 0x1,
2574                    Condition::HS => 0x2,
2575                    Condition::LO => 0x3,
2576                    Condition::HI => 0x8,
2577                    Condition::LS => 0x9,
2578                    Condition::GE => 0xA,
2579                    Condition::LT => 0xB,
2580                    Condition::GT => 0xC,
2581                    Condition::LE => 0xD,
2582                };
2583                // 16-bit B<cond> with offset 0: 1101 cond imm8
2584                let instr: u16 = 0xD000 | (cond_bits << 8);
2585                Ok(instr.to_le_bytes().to_vec())
2586            }
2587
2588            // Branch instructions
2589            ArmOp::B { label: _ } => {
2590                // Simplified: B.N with offset 0
2591                // For real usage, would need label resolution
2592                let instr: u16 = 0xE000; // B.N #0
2593                Ok(instr.to_le_bytes().to_vec())
2594            }
2595
2596            // BHS (Branch if Higher or Same) - used for bounds checking
2597            // Condition code: 0x2 (C set)
2598            ArmOp::Bhs { label: _ } => {
2599                // 16-bit B<cond> with offset 0: 1101 cond imm8
2600                // cond = 0x2 (HS)
2601                let instr: u16 = 0xD200; // BHS.N #0
2602                Ok(instr.to_le_bytes().to_vec())
2603            }
2604
2605            // BLO (Branch if Lower) - complementary to BHS
2606            // Condition code: 0x3 (C clear)
2607            ArmOp::Blo { label: _ } => {
2608                // 16-bit B<cond> with offset 0: 1101 cond imm8
2609                // cond = 0x3 (LO)
2610                let instr: u16 = 0xD300; // BLO.N #0
2611                Ok(instr.to_le_bytes().to_vec())
2612            }
2613
2614            // Branch with numeric offset (Thumb-2)
2615            // Thumb-2 B.W instruction: 32-bit with +-16MB range
2616            ArmOp::BOffset { offset } => {
2617                // offset is already the halfword displacement: (target - branch - 4) / 2
2618                // This is the raw encoded value, accounting for variable-length instructions
2619                let halfword_offset = *offset;
2620
2621                // 16-bit B.N encoding: 1110 0 imm11 (11-bit signed halfword offset)
2622                // Range: -1024 to +1022 halfwords
2623                if (-1024..=1022).contains(&halfword_offset) {
2624                    // 16-bit B.N encoding: 1110 0 imm11
2625                    let imm11 = (halfword_offset as u16) & 0x7FF;
2626                    let instr: u16 = 0xE000 | imm11;
2627                    Ok(instr.to_le_bytes().to_vec())
2628                } else {
2629                    // 32-bit B.W encoding for larger offsets
2630                    // First halfword: 1111 0 S imm10
2631                    // Second halfword: 10 J1 0 J2 imm11
2632                    // Total offset = SignExtend(S:I1:I2:imm10:imm11:0)
2633                    // where I1 = NOT(J1 XOR S), I2 = NOT(J2 XOR S)
2634
2635                    // The B.W (T4) encoding packs the signed offset as:
2636                    //   S:I1:I2:imm10:imm11:0  (25-bit signed, halfword-aligned)
2637                    // where J1 = NOT(I1 XOR S), J2 = NOT(I2 XOR S)
2638                    // Input halfword_offset already equals (target - PC - 4) / 2,
2639                    // so the full byte offset = halfword_offset << 1.
2640                    // The encoding fields split that 25-bit signed value (including the
2641                    // implicit trailing zero) as: S | imm10 | imm11
2642                    // with I1 = bit 23 and I2 = bit 22 of the signed offset.
2643                    let signed_offset = halfword_offset << 1; // byte offset
2644                    let s = if signed_offset < 0 { 1u32 } else { 0u32 };
2645                    let uoffset = signed_offset as u32;
2646                    let imm10 = (uoffset >> 12) & 0x3FF; // bits [21:12]
2647                    let imm11 = (uoffset >> 1) & 0x7FF; // bits [11:1]
2648                    let i1 = (uoffset >> 23) & 1; // bit 23
2649                    let i2 = (uoffset >> 22) & 1; // bit 22
2650                    let j1 = (!(i1 ^ s)) & 1; // J1 = NOT(I1 XOR S)
2651                    let j2 = (!(i2 ^ s)) & 1; // J2 = NOT(I2 XOR S)
2652
2653                    let hw1: u16 = (0xF000 | (s << 10) | imm10) as u16;
2654                    let hw2: u16 = (0x9000 | (j1 << 13) | (j2 << 11) | imm11) as u16;
2655
2656                    let mut bytes = hw1.to_le_bytes().to_vec();
2657                    bytes.extend_from_slice(&hw2.to_le_bytes());
2658                    Ok(bytes)
2659                }
2660            }
2661
2662            // Conditional branch with numeric offset (Thumb-2)
2663            ArmOp::BCondOffset { cond, offset } => {
2664                use synth_synthesis::Condition;
2665                let cond_bits: u16 = match cond {
2666                    Condition::EQ => 0x0,
2667                    Condition::NE => 0x1,
2668                    Condition::HS => 0x2,
2669                    Condition::LO => 0x3,
2670                    Condition::HI => 0x8,
2671                    Condition::LS => 0x9,
2672                    Condition::GE => 0xA,
2673                    Condition::LT => 0xB,
2674                    Condition::GT => 0xC,
2675                    Condition::LE => 0xD,
2676                };
2677
2678                // offset is already the halfword displacement: (target - branch - 4) / 2
2679                // This is the raw imm8 value for 16-bit B<cond> encoding
2680                let halfword_offset = *offset;
2681
2682                // 16-bit B<cond> encoding: 1101 cond imm8
2683                // Range: -256 to +254 halfwords (imm8 is sign-extended and shifted left 1)
2684                if (-128..=127).contains(&halfword_offset) {
2685                    let imm8 = (halfword_offset as u16) & 0xFF;
2686                    let instr: u16 = 0xD000 | (cond_bits << 8) | imm8;
2687                    Ok(instr.to_le_bytes().to_vec())
2688                } else {
2689                    // 32-bit B<cond>.W for larger offsets
2690                    // First halfword: 1111 0 S cond imm6
2691                    // Second halfword: 10 J1 0 J2 imm11
2692                    let offset = halfword_offset >> 1;
2693                    let s = if offset < 0 { 1u32 } else { 0u32 };
2694                    let imm6 = ((offset >> 11) as u32) & 0x3F;
2695                    let imm11 = (offset as u32) & 0x7FF;
2696                    let j1 = if s == 1 { 1 } else { 0 };
2697                    let j2 = if s == 1 { 1 } else { 0 };
2698
2699                    let hw1: u16 = (0xF000 | (s << 10) | ((cond_bits as u32) << 6) | imm6) as u16;
2700                    let hw2: u16 = (0x8000 | (j1 << 13) | (j2 << 11) | imm11) as u16;
2701
2702                    let mut bytes = hw1.to_le_bytes().to_vec();
2703                    bytes.extend_from_slice(&hw2.to_le_bytes());
2704                    Ok(bytes)
2705                }
2706            }
2707
2708            ArmOp::Bl { label: _ } => {
2709                // BL is always 32-bit in Thumb-2, encoded here as a relocatable
2710                // placeholder; an R_ARM_THM_CALL relocation patches the target
2711                // (see arm_backend.rs). The placeholder must carry an embedded
2712                // addend of -4 so the relocation nets to exactly the symbol S.
2713                //
2714                // Thumb BL computes `target = (P + 4) + signed_offset`. Under
2715                // R_ARM_THM_CALL the linker resolves using the in-place addend;
2716                // a 0xF800 placeholder (addend 0) lands at S+4 — every call one
2717                // instruction past the callee entry (#174). The correct
2718                // placeholder is what `gas` emits for `bl <extern>`:
2719                //   f7ff fffe  ->  `bl <self>`  (S=1, J1=J2=1, imm = -4 addend),
2720                // i.e. hw1=0xF7FF, hw2=0xFFFE. This nets to S, not S+4.
2721                // (The earlier 0xD000 was worse still — a ~+0x600000 addend,
2722                // the garbage `bl c0000c` and "truncated to fit" of #167.)
2723                let hw1: u16 = 0xF7FF;
2724                let hw2: u16 = 0xFFFE;
2725                let mut bytes = hw1.to_le_bytes().to_vec();
2726                bytes.extend_from_slice(&hw2.to_le_bytes());
2727                Ok(bytes)
2728            }
2729
2730            // MVN
2731            ArmOp::Mvn { rd, op2 } => {
2732                if let Operand2::Reg(rm) = op2 {
2733                    let rd_bits = reg_to_bits(rd) as u16;
2734                    let rm_bits = reg_to_bits(rm) as u16;
2735
2736                    if rd_bits < 8 && rm_bits < 8 {
2737                        // MVNS Rd, Rm (16-bit): 0100 0011 11 Rm Rd
2738                        let instr: u16 = 0x43C0 | (rm_bits << 3) | rd_bits;
2739                        Ok(instr.to_le_bytes().to_vec())
2740                    } else {
2741                        // 32-bit MVN
2742                        let hw1: u16 = 0xEA6F_u16;
2743                        let hw2: u16 = ((reg_to_bits(rd) << 8) | reg_to_bits(rm)) as u16;
2744                        let mut bytes = hw1.to_le_bytes().to_vec();
2745                        bytes.extend_from_slice(&hw2.to_le_bytes());
2746                        Ok(bytes)
2747                    }
2748                } else {
2749                    let instr: u16 = 0xBF00;
2750                    Ok(instr.to_le_bytes().to_vec())
2751                }
2752            }
2753
2754            // MOVW - Move Wide (Thumb-2 32-bit)
2755            ArmOp::Movw { rd, imm16 } => {
2756                self.encode_thumb32_movw_raw(reg_to_bits(rd), *imm16 as u32)
2757            }
2758
2759            // MOVT - Move Top (Thumb-2 32-bit)
2760            ArmOp::Movt { rd, imm16 } => {
2761                self.encode_thumb32_movt_raw(reg_to_bits(rd), *imm16 as u32)
2762            }
2763
2764            // #237: symbol-relative MOVW/MOVT. Encode the addend's low/high 16
2765            // bits in place; the backend records an R_ARM_MOVW_ABS_NC /
2766            // R_ARM_MOVT_ABS relocation against `symbol`, so the linker adds the
2767            // symbol's final address to the in-place addend (REL semantics).
2768            ArmOp::MovwSym { rd, addend, .. } => {
2769                self.encode_thumb32_movw_raw(reg_to_bits(rd), (*addend as u32) & 0xffff)
2770            }
2771            ArmOp::MovtSym { rd, addend, .. } => {
2772                self.encode_thumb32_movt_raw(reg_to_bits(rd), ((*addend as u32) >> 16) & 0xffff)
2773            }
2774
2775            // SetCond: Materialize condition flag into register (0 or 1)
2776            // Strategy: ITE <cond>; MOV Rd, #1; MOV Rd, #0
2777            // IMPORTANT: Must use ITE (If-Then-Else) because 16-bit Thumb MOV
2778            // always sets flags (MOVS). We need to evaluate the condition BEFORE
2779            // any MOV instruction clobbers the flags from CMP.
2780            ArmOp::SetCond { rd, cond } => {
2781                let rd_bits = reg_to_bits(rd) as u16;
2782
2783                // Condition code encoding for IT block
2784                use synth_synthesis::Condition;
2785                let cond_bits: u16 = match cond {
2786                    Condition::EQ => 0x0,
2787                    Condition::NE => 0x1,
2788                    Condition::LT => 0xB,
2789                    Condition::LE => 0xD,
2790                    Condition::GT => 0xC,
2791                    Condition::GE => 0xA,
2792                    Condition::LO => 0x3, // CC/LO (unsigned <)
2793                    Condition::LS => 0x9, // LS (unsigned <=)
2794                    Condition::HI => 0x8, // HI (unsigned >)
2795                    Condition::HS => 0x2, // CS/HS (unsigned >=)
2796                };
2797
2798                // ITE <cond>: encodes If-Then-Else block
2799                // The mask field depends on firstcond[0]:
2800                // - If firstcond[0] = 0: mask = 0xC for TE pattern (ITE EQ = BF0C)
2801                // - If firstcond[0] = 1: mask = 0x4 for TE pattern (ITE NE = BF14)
2802                let mask = if (cond_bits & 1) == 0 { 0xC } else { 0x4 };
2803                let ite_instr: u16 = 0xBF00 | (cond_bits << 4) | mask;
2804
2805                // Materialize 0/1 into Rd. The 16-bit MOVS (T1) encodes Rd in a
2806                // 3-bit field (bits[10:8]) — only R0–R7. For a high register
2807                // (R8–R12) `rd_bits << 8` overflows into bit 11 and silently
2808                // turns MOVS into CMP (00100 → 00101), corrupting the result
2809                // (this mis-materialized gale's `has_waiter`, so its `local.set`
2810                // stored a stale register → the binary-sem WAKE dispatch read
2811                // garbage). Use the 32-bit MOV.W (T2) for high registers, which
2812                // has a 4-bit Rd field. MOV.W with S=0 doesn't set flags, which
2813                // is fine inside the ITE (the materialized value is the result;
2814                // the flags are not consumed afterwards).
2815                let mut bytes = ite_instr.to_le_bytes().to_vec();
2816                let push_mov = |bytes: &mut Vec<u8>, imm: u16| {
2817                    if rd_bits <= 7 {
2818                        let m: u16 = 0x2000 | (rd_bits << 8) | imm; // 16-bit MOVS Rd,#imm
2819                        bytes.extend_from_slice(&m.to_le_bytes());
2820                    } else {
2821                        // 32-bit MOV.W Rd, #imm (T2): F04F | (Rd<<8) | imm8
2822                        let hw1: u16 = 0xF04F;
2823                        let hw2: u16 = (rd_bits << 8) | imm;
2824                        bytes.extend_from_slice(&hw1.to_le_bytes());
2825                        bytes.extend_from_slice(&hw2.to_le_bytes());
2826                    }
2827                };
2828                push_mov(&mut bytes, 1); // Then branch (condition true)  → 1
2829                push_mov(&mut bytes, 0); // Else branch (condition false) → 0
2830                Ok(bytes)
2831            }
2832
2833            // I64SetCond: Compare two i64 register pairs, result 0/1 in rd
2834            // EQ/NE: CMP lo,lo; IT EQ; CMPEQ hi,hi; ITE <cond>; MOV 1; MOV 0
2835            // LT: CMP lo,lo; SBCS rd,hi,hi; ITE LT; MOV 1; MOV 0
2836            // GT: CMP lo,lo (swapped); SBCS rd,hi,hi (swapped); ITE LT; MOV 1; MOV 0
2837            ArmOp::I64SetCond {
2838                rd,
2839                rn_lo,
2840                rn_hi,
2841                rm_lo,
2842                rm_hi,
2843                cond,
2844            } => {
2845                use synth_synthesis::Condition;
2846                let rd_bits = reg_to_bits(rd) as u16;
2847                let mut bytes = Vec::new();
2848
2849                // Helper: encode CMP Rn, Rm (16-bit)
2850                let encode_cmp_reg = |rn: &synth_synthesis::Reg,
2851                                      rm: &synth_synthesis::Reg|
2852                 -> Vec<u8> {
2853                    let rn_bits = reg_to_bits(rn) as u16;
2854                    let rm_bits = reg_to_bits(rm) as u16;
2855                    if rn_bits < 8 && rm_bits < 8 {
2856                        let instr: u16 = 0x4280 | (rm_bits << 3) | rn_bits;
2857                        instr.to_le_bytes().to_vec()
2858                    } else {
2859                        let n_bit = (rn_bits >> 3) & 1;
2860                        let instr: u16 = 0x4500 | (n_bit << 7) | (rm_bits << 3) | (rn_bits & 0x7);
2861                        instr.to_le_bytes().to_vec()
2862                    }
2863                };
2864
2865                // Helper: encode ITE <cond> (2 bytes)
2866                let encode_ite = |cond_bits: u16| -> Vec<u8> {
2867                    let mask = if (cond_bits & 1) == 0 { 0xC } else { 0x4 };
2868                    let ite_instr: u16 = 0xBF00 | (cond_bits << 4) | mask;
2869                    ite_instr.to_le_bytes().to_vec()
2870                };
2871
2872                // Helper: encode SetCond (ITE + MOV #1 + MOV #0) for given condition
2873                let encode_setcond = |cond_bits: u16, rd_bits: u16| -> Vec<u8> {
2874                    let mut b = encode_ite(cond_bits);
2875                    let mov_one: u16 = 0x2001 | (rd_bits << 8);
2876                    let mov_zero: u16 = 0x2000 | (rd_bits << 8);
2877                    b.extend_from_slice(&mov_one.to_le_bytes());
2878                    b.extend_from_slice(&mov_zero.to_le_bytes());
2879                    b
2880                };
2881
2882                match cond {
2883                    Condition::EQ | Condition::NE => {
2884                        // CMP rn_lo, rm_lo (compare low words)
2885                        bytes.extend_from_slice(&encode_cmp_reg(rn_lo, rm_lo));
2886
2887                        // IT EQ (execute next instruction only if Z=1)
2888                        let it_eq: u16 = 0xBF08; // IT EQ: cond=0000, mask=1000
2889                        bytes.extend_from_slice(&it_eq.to_le_bytes());
2890
2891                        // CMPEQ rn_hi, rm_hi (compare high words, only if low equal)
2892                        bytes.extend_from_slice(&encode_cmp_reg(rn_hi, rm_hi));
2893
2894                        // ITE <cond>; MOV rd, #1; MOV rd, #0
2895                        let cond_bits: u16 = match cond {
2896                            Condition::EQ => 0x0,
2897                            Condition::NE => 0x1,
2898                            _ => unreachable!(),
2899                        };
2900                        bytes.extend_from_slice(&encode_setcond(cond_bits, rd_bits));
2901                    }
2902
2903                    Condition::LT => {
2904                        // CMP rn_lo, rm_lo (sets C flag for borrow)
2905                        bytes.extend_from_slice(&encode_cmp_reg(rn_lo, rm_lo));
2906
2907                        // SBCS rd, rn_hi, rm_hi (subtract with carry, sets N,V flags)
2908                        // SBCS.W Rd, Rn, Rm: EB70 Rn | 0000 Rd 0000 Rm
2909                        let rn_hi_bits = reg_to_bits(rn_hi);
2910                        let rm_hi_bits = reg_to_bits(rm_hi);
2911                        let hw1: u16 = (0xEB70 | rn_hi_bits) as u16;
2912                        let hw2: u16 = ((rd_bits as u32) << 8 | rm_hi_bits) as u16;
2913                        bytes.extend_from_slice(&hw1.to_le_bytes());
2914                        bytes.extend_from_slice(&hw2.to_le_bytes());
2915
2916                        // ITE LT; MOV rd, #1; MOV rd, #0
2917                        bytes.extend_from_slice(&encode_setcond(0xB, rd_bits)); // LT = 0xB
2918                    }
2919
2920                    Condition::GT => {
2921                        // GT(a,b) = LT(b,a): swap operands
2922                        // CMP rm_lo, rn_lo (swapped)
2923                        bytes.extend_from_slice(&encode_cmp_reg(rm_lo, rn_lo));
2924
2925                        // SBCS rd, rm_hi, rn_hi (swapped)
2926                        let rm_hi_bits = reg_to_bits(rm_hi);
2927                        let rn_hi_bits = reg_to_bits(rn_hi);
2928                        let hw1: u16 = (0xEB70 | rm_hi_bits) as u16;
2929                        let hw2: u16 = ((rd_bits as u32) << 8 | rn_hi_bits) as u16;
2930                        bytes.extend_from_slice(&hw1.to_le_bytes());
2931                        bytes.extend_from_slice(&hw2.to_le_bytes());
2932
2933                        // ITE LT; MOV rd, #1; MOV rd, #0
2934                        bytes.extend_from_slice(&encode_setcond(0xB, rd_bits)); // LT = 0xB
2935                    }
2936
2937                    Condition::LE => {
2938                        // LE(a,b) = !GT(a,b): use GT logic but invert result
2939                        // GT(a,b) = LT(b,a): so we do CMP(b,a) and check LT, then invert
2940                        // CMP rm_lo, rn_lo (swapped, same as GT)
2941                        bytes.extend_from_slice(&encode_cmp_reg(rm_lo, rn_lo));
2942
2943                        // SBCS rd, rm_hi, rn_hi (swapped)
2944                        let rm_hi_bits = reg_to_bits(rm_hi);
2945                        let rn_hi_bits = reg_to_bits(rn_hi);
2946                        let hw1: u16 = (0xEB70 | rm_hi_bits) as u16;
2947                        let hw2: u16 = ((rd_bits as u32) << 8 | rn_hi_bits) as u16;
2948                        bytes.extend_from_slice(&hw1.to_le_bytes());
2949                        bytes.extend_from_slice(&hw2.to_le_bytes());
2950
2951                        // ITE GE; MOV rd, #1; MOV rd, #0 (GE is !LT, so inverting GT result)
2952                        bytes.extend_from_slice(&encode_setcond(0xA, rd_bits)); // GE = 0xA
2953                    }
2954
2955                    Condition::GE => {
2956                        // GE(a,b) = !LT(a,b): use LT logic but invert result
2957                        // CMP rn_lo, rm_lo (same as LT)
2958                        bytes.extend_from_slice(&encode_cmp_reg(rn_lo, rm_lo));
2959
2960                        // SBCS rd, rn_hi, rm_hi (same as LT)
2961                        let rn_hi_bits = reg_to_bits(rn_hi);
2962                        let rm_hi_bits = reg_to_bits(rm_hi);
2963                        let hw1: u16 = (0xEB70 | rn_hi_bits) as u16;
2964                        let hw2: u16 = ((rd_bits as u32) << 8 | rm_hi_bits) as u16;
2965                        bytes.extend_from_slice(&hw1.to_le_bytes());
2966                        bytes.extend_from_slice(&hw2.to_le_bytes());
2967
2968                        // ITE GE; MOV rd, #1; MOV rd, #0 (GE is !LT)
2969                        bytes.extend_from_slice(&encode_setcond(0xA, rd_bits)); // GE = 0xA
2970                    }
2971
2972                    // Unsigned comparisons - same instruction sequence, different conditions
2973                    Condition::LO => {
2974                        // LO (unsigned LT): CMP lo, SBCS hi, check C=0
2975                        bytes.extend_from_slice(&encode_cmp_reg(rn_lo, rm_lo));
2976                        let rn_hi_bits = reg_to_bits(rn_hi);
2977                        let rm_hi_bits = reg_to_bits(rm_hi);
2978                        let hw1: u16 = (0xEB70 | rn_hi_bits) as u16;
2979                        let hw2: u16 = ((rd_bits as u32) << 8 | rm_hi_bits) as u16;
2980                        bytes.extend_from_slice(&hw1.to_le_bytes());
2981                        bytes.extend_from_slice(&hw2.to_le_bytes());
2982                        bytes.extend_from_slice(&encode_setcond(0x3, rd_bits)); // LO = 0x3 (CC)
2983                    }
2984
2985                    Condition::HI => {
2986                        // HI (unsigned GT): swap operands and check LO
2987                        bytes.extend_from_slice(&encode_cmp_reg(rm_lo, rn_lo));
2988                        let rm_hi_bits = reg_to_bits(rm_hi);
2989                        let rn_hi_bits = reg_to_bits(rn_hi);
2990                        let hw1: u16 = (0xEB70 | rm_hi_bits) as u16;
2991                        let hw2: u16 = ((rd_bits as u32) << 8 | rn_hi_bits) as u16;
2992                        bytes.extend_from_slice(&hw1.to_le_bytes());
2993                        bytes.extend_from_slice(&hw2.to_le_bytes());
2994                        bytes.extend_from_slice(&encode_setcond(0x3, rd_bits)); // LO = 0x3 (CC)
2995                    }
2996
2997                    Condition::LS => {
2998                        // LS (unsigned LE): !(a > b) = !(HI), so do HI and invert
2999                        bytes.extend_from_slice(&encode_cmp_reg(rm_lo, rn_lo));
3000                        let rm_hi_bits = reg_to_bits(rm_hi);
3001                        let rn_hi_bits = reg_to_bits(rn_hi);
3002                        let hw1: u16 = (0xEB70 | rm_hi_bits) as u16;
3003                        let hw2: u16 = ((rd_bits as u32) << 8 | rn_hi_bits) as u16;
3004                        bytes.extend_from_slice(&hw1.to_le_bytes());
3005                        bytes.extend_from_slice(&hw2.to_le_bytes());
3006                        bytes.extend_from_slice(&encode_setcond(0x2, rd_bits)); // HS = 0x2 (CS) = !LO
3007                    }
3008
3009                    Condition::HS => {
3010                        // HS (unsigned GE): !(a < b) = !(LO)
3011                        bytes.extend_from_slice(&encode_cmp_reg(rn_lo, rm_lo));
3012                        let rn_hi_bits = reg_to_bits(rn_hi);
3013                        let rm_hi_bits = reg_to_bits(rm_hi);
3014                        let hw1: u16 = (0xEB70 | rn_hi_bits) as u16;
3015                        let hw2: u16 = ((rd_bits as u32) << 8 | rm_hi_bits) as u16;
3016                        bytes.extend_from_slice(&hw1.to_le_bytes());
3017                        bytes.extend_from_slice(&hw2.to_le_bytes());
3018                        bytes.extend_from_slice(&encode_setcond(0x2, rd_bits)); // HS = 0x2 (CS) = !LO
3019                    }
3020                }
3021
3022                Ok(bytes)
3023            }
3024
3025            // I64SetCondZ: Test if i64 register pair is zero, result 0/1 in rd
3026            // ORR.W rd, rn_lo, rn_hi; CMP rd, #0; ITE EQ; MOV 1; MOV 0
3027            ArmOp::I64SetCondZ { rd, rn_lo, rn_hi } => {
3028                let rd_bits = reg_to_bits(rd);
3029                let rn_lo_bits = reg_to_bits(rn_lo);
3030                let rn_hi_bits = reg_to_bits(rn_hi);
3031                let mut bytes = Vec::new();
3032
3033                // ORR.W rd, rn_lo, rn_hi: EA40 rn_lo | 0000 rd 0000 rn_hi
3034                let hw1: u16 = (0xEA40 | rn_lo_bits) as u16;
3035                let hw2: u16 = ((rd_bits << 8) | rn_hi_bits) as u16;
3036                bytes.extend_from_slice(&hw1.to_le_bytes());
3037                bytes.extend_from_slice(&hw2.to_le_bytes());
3038
3039                // CMP rd, #0 (16-bit): 0010 1 Rd 0000 0000
3040                let cmp_instr: u16 = 0x2800 | ((rd_bits as u16) << 8);
3041                bytes.extend_from_slice(&cmp_instr.to_le_bytes());
3042
3043                // ITE EQ; MOV rd, #1; MOV rd, #0
3044                let mask = 0xC_u16; // ITE EQ mask: firstcond[0]=0, mask=0xC
3045                let ite_instr: u16 = 0xBF00 | mask;
3046                bytes.extend_from_slice(&ite_instr.to_le_bytes());
3047                let mov_one: u16 = 0x2001 | ((rd_bits as u16) << 8);
3048                let mov_zero: u16 = 0x2000 | ((rd_bits as u16) << 8);
3049                bytes.extend_from_slice(&mov_one.to_le_bytes());
3050                bytes.extend_from_slice(&mov_zero.to_le_bytes());
3051
3052                Ok(bytes)
3053            }
3054
3055            // I64Mul: 64-bit multiply using UMULL + MLA cross products
3056            // Formula: result = (a_lo * b_lo) + ((a_lo * b_hi + a_hi * b_lo) << 32)
3057            // Uses R12 as scratch register
3058            ArmOp::I64Mul {
3059                rd_lo,
3060                rd_hi,
3061                rn_lo,
3062                rn_hi,
3063                rm_lo,
3064                rm_hi,
3065            } => {
3066                let rd_lo_bits = reg_to_bits(rd_lo);
3067                let rd_hi_bits = reg_to_bits(rd_hi);
3068                let rn_lo_bits = reg_to_bits(rn_lo);
3069                let rn_hi_bits = reg_to_bits(rn_hi);
3070                let rm_lo_bits = reg_to_bits(rm_lo);
3071                let rm_hi_bits = reg_to_bits(rm_hi);
3072                let r12: u32 = 12; // IP scratch register
3073                let mut bytes = Vec::new();
3074
3075                // 1. MUL R12, rn_lo, rm_hi  (R12 = a_lo * b_hi)
3076                // Thumb-2 MUL: hw1=0xFB00|Rn, hw2=0xF000|(Rd<<8)|Rm
3077                let hw1: u16 = (0xFB00 | rn_lo_bits) as u16;
3078                let hw2: u16 = (0xF000 | (r12 << 8) | rm_hi_bits) as u16;
3079                bytes.extend_from_slice(&hw1.to_le_bytes());
3080                bytes.extend_from_slice(&hw2.to_le_bytes());
3081
3082                // 2. MLA R12, rn_hi, rm_lo, R12  (R12 += a_hi * b_lo)
3083                // Thumb-2 MLA: hw1=0xFB00|Rn, hw2=(Ra<<12)|(Rd<<8)|Rm
3084                let hw1: u16 = (0xFB00 | rn_hi_bits) as u16;
3085                let hw2: u16 = ((r12 << 12) | (r12 << 8) | rm_lo_bits) as u16;
3086                bytes.extend_from_slice(&hw1.to_le_bytes());
3087                bytes.extend_from_slice(&hw2.to_le_bytes());
3088
3089                // 3. UMULL rd_lo, rd_hi, rn_lo, rm_lo  (rd_lo:rd_hi = a_lo * b_lo)
3090                // Thumb-2 UMULL: hw1=0xFBA0|Rn, hw2=(RdLo<<12)|(RdHi<<8)|Rm
3091                let hw1: u16 = (0xFBA0 | rn_lo_bits) as u16;
3092                let hw2: u16 = ((rd_lo_bits << 12) | (rd_hi_bits << 8) | rm_lo_bits) as u16;
3093                bytes.extend_from_slice(&hw1.to_le_bytes());
3094                bytes.extend_from_slice(&hw2.to_le_bytes());
3095
3096                // 4. ADD rd_hi, R12  (rd_hi += cross products)
3097                // 16-bit high reg ADD: 01000100 D Rm Rdn[2:0]
3098                let d_bit = (rd_hi_bits >> 3) & 1;
3099                let add_instr: u16 =
3100                    (0x4400 | (d_bit << 7) | (r12 << 3) | (rd_hi_bits & 0x7)) as u16;
3101                bytes.extend_from_slice(&add_instr.to_le_bytes());
3102
3103                Ok(bytes)
3104            }
3105
3106            // I64Shl: 64-bit shift left with branch for n<32 vs n>=32
3107            // rm_hi (R3) is used as temp register
3108            ArmOp::I64Shl {
3109                rd_lo,
3110                rd_hi,
3111                rn_lo,
3112                rn_hi,
3113                rm_lo,
3114                rm_hi,
3115            } => {
3116                let rd_lo_bits = reg_to_bits(rd_lo);
3117                let rd_hi_bits = reg_to_bits(rd_hi);
3118                let rn_lo_bits = reg_to_bits(rn_lo);
3119                let rn_hi_bits = reg_to_bits(rn_hi);
3120                let rm_lo_bits = reg_to_bits(rm_lo);
3121                let rm_hi_bits = reg_to_bits(rm_hi); // temp
3122                let mut bytes = Vec::new();
3123
3124                // AND.W rm_lo, rm_lo, #63  (mask shift amount to 6 bits)
3125                let hw1: u16 = (0xF000 | rm_lo_bits) as u16;
3126                let hw2: u16 = ((rm_lo_bits << 8) | 0x3F) as u16;
3127                bytes.extend_from_slice(&hw1.to_le_bytes());
3128                bytes.extend_from_slice(&hw2.to_le_bytes());
3129
3130                // SUBS.W rm_hi, rm_lo, #32  (rm_hi = n-32, sets flags)
3131                let hw1: u16 = (0xF1B0 | rm_lo_bits) as u16;
3132                let hw2: u16 = ((rm_hi_bits << 8) | 0x20) as u16;
3133                bytes.extend_from_slice(&hw1.to_le_bytes());
3134                bytes.extend_from_slice(&hw2.to_le_bytes());
3135
3136                // BPL .large (branch if n >= 32, offset = +10 halfwords)
3137                let bpl: u16 = 0xD50A;
3138                bytes.extend_from_slice(&bpl.to_le_bytes());
3139
3140                // --- Small shift (n < 32) ---
3141                // RSB.W rm_hi, rm_lo, #32  (rm_hi = 32-n)
3142                let hw1: u16 = (0xF1C0 | rm_lo_bits) as u16;
3143                let hw2: u16 = ((rm_hi_bits << 8) | 0x20) as u16;
3144                bytes.extend_from_slice(&hw1.to_le_bytes());
3145                bytes.extend_from_slice(&hw2.to_le_bytes());
3146
3147                // LSR.W rm_hi, rn_lo, rm_hi  (rm_hi = lo >> (32-n), overflow bits)
3148                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3149                let hw2: u16 = (0xF000 | (rm_hi_bits << 8) | rm_hi_bits) as u16;
3150                bytes.extend_from_slice(&hw1.to_le_bytes());
3151                bytes.extend_from_slice(&hw2.to_le_bytes());
3152
3153                // LSL.W rd_hi, rn_hi, rm_lo  (hi <<= n)
3154                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3155                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | rm_lo_bits) as u16;
3156                bytes.extend_from_slice(&hw1.to_le_bytes());
3157                bytes.extend_from_slice(&hw2.to_le_bytes());
3158
3159                // ORR.W rd_hi, rd_hi, rm_hi  (hi |= overflow bits from lo)
3160                let hw1: u16 = (0xEA40 | rd_hi_bits) as u16;
3161                let hw2: u16 = ((rd_hi_bits << 8) | rm_hi_bits) as u16;
3162                bytes.extend_from_slice(&hw1.to_le_bytes());
3163                bytes.extend_from_slice(&hw2.to_le_bytes());
3164
3165                // LSL.W rd_lo, rn_lo, rm_lo  (lo <<= n)
3166                let hw1: u16 = (0xFA00 | rn_lo_bits) as u16;
3167                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | rm_lo_bits) as u16;
3168                bytes.extend_from_slice(&hw1.to_le_bytes());
3169                bytes.extend_from_slice(&hw2.to_le_bytes());
3170
3171                // B .done (skip large shift: +2 halfwords)
3172                let b_done: u16 = 0xE002;
3173                bytes.extend_from_slice(&b_done.to_le_bytes());
3174
3175                // --- Large shift (n >= 32) ---
3176                // LSL.W rd_hi, rn_lo, rm_hi  (hi = lo << (n-32))
3177                let hw1: u16 = (0xFA00 | rn_lo_bits) as u16;
3178                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | rm_hi_bits) as u16;
3179                bytes.extend_from_slice(&hw1.to_le_bytes());
3180                bytes.extend_from_slice(&hw2.to_le_bytes());
3181
3182                // MOV rd_lo, #0
3183                let mov_zero: u16 = 0x2000 | ((rd_lo_bits as u16) << 8);
3184                bytes.extend_from_slice(&mov_zero.to_le_bytes());
3185
3186                Ok(bytes) // Total: 38 bytes
3187            }
3188
3189            // I64ShrU: 64-bit logical shift right with branch for n<32 vs n>=32
3190            ArmOp::I64ShrU {
3191                rd_lo,
3192                rd_hi,
3193                rn_lo,
3194                rn_hi,
3195                rm_lo,
3196                rm_hi,
3197            } => {
3198                let rd_lo_bits = reg_to_bits(rd_lo);
3199                let rd_hi_bits = reg_to_bits(rd_hi);
3200                let rn_lo_bits = reg_to_bits(rn_lo);
3201                let rn_hi_bits = reg_to_bits(rn_hi);
3202                let rm_lo_bits = reg_to_bits(rm_lo);
3203                let rm_hi_bits = reg_to_bits(rm_hi); // temp
3204                let mut bytes = Vec::new();
3205
3206                // AND.W rm_lo, rm_lo, #63
3207                let hw1: u16 = (0xF000 | rm_lo_bits) as u16;
3208                let hw2: u16 = ((rm_lo_bits << 8) | 0x3F) as u16;
3209                bytes.extend_from_slice(&hw1.to_le_bytes());
3210                bytes.extend_from_slice(&hw2.to_le_bytes());
3211
3212                // SUBS.W rm_hi, rm_lo, #32
3213                let hw1: u16 = (0xF1B0 | rm_lo_bits) as u16;
3214                let hw2: u16 = ((rm_hi_bits << 8) | 0x20) as u16;
3215                bytes.extend_from_slice(&hw1.to_le_bytes());
3216                bytes.extend_from_slice(&hw2.to_le_bytes());
3217
3218                // BPL .large (+10 halfwords)
3219                let bpl: u16 = 0xD50A;
3220                bytes.extend_from_slice(&bpl.to_le_bytes());
3221
3222                // --- Small shift (n < 32) ---
3223                // RSB.W rm_hi, rm_lo, #32  (rm_hi = 32-n)
3224                let hw1: u16 = (0xF1C0 | rm_lo_bits) as u16;
3225                let hw2: u16 = ((rm_hi_bits << 8) | 0x20) as u16;
3226                bytes.extend_from_slice(&hw1.to_le_bytes());
3227                bytes.extend_from_slice(&hw2.to_le_bytes());
3228
3229                // LSL.W rm_hi, rn_hi, rm_hi  (rm_hi = hi << (32-n), bits flowing to lo)
3230                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3231                let hw2: u16 = (0xF000 | (rm_hi_bits << 8) | rm_hi_bits) as u16;
3232                bytes.extend_from_slice(&hw1.to_le_bytes());
3233                bytes.extend_from_slice(&hw2.to_le_bytes());
3234
3235                // LSR.W rd_lo, rn_lo, rm_lo  (lo >>= n)
3236                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3237                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | rm_lo_bits) as u16;
3238                bytes.extend_from_slice(&hw1.to_le_bytes());
3239                bytes.extend_from_slice(&hw2.to_le_bytes());
3240
3241                // ORR.W rd_lo, rd_lo, rm_hi  (lo |= overflow from hi)
3242                let hw1: u16 = (0xEA40 | rd_lo_bits) as u16;
3243                let hw2: u16 = ((rd_lo_bits << 8) | rm_hi_bits) as u16;
3244                bytes.extend_from_slice(&hw1.to_le_bytes());
3245                bytes.extend_from_slice(&hw2.to_le_bytes());
3246
3247                // LSR.W rd_hi, rn_hi, rm_lo  (hi >>= n, logical)
3248                let hw1: u16 = (0xFA20 | rn_hi_bits) as u16;
3249                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | rm_lo_bits) as u16;
3250                bytes.extend_from_slice(&hw1.to_le_bytes());
3251                bytes.extend_from_slice(&hw2.to_le_bytes());
3252
3253                // B .done (+2 halfwords)
3254                let b_done: u16 = 0xE002;
3255                bytes.extend_from_slice(&b_done.to_le_bytes());
3256
3257                // --- Large shift (n >= 32) ---
3258                // LSR.W rd_lo, rn_hi, rm_hi  (lo = hi >> (n-32))
3259                let hw1: u16 = (0xFA20 | rn_hi_bits) as u16;
3260                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | rm_hi_bits) as u16;
3261                bytes.extend_from_slice(&hw1.to_le_bytes());
3262                bytes.extend_from_slice(&hw2.to_le_bytes());
3263
3264                // MOV rd_hi, #0
3265                let mov_zero: u16 = 0x2000 | ((rd_hi_bits as u16) << 8);
3266                bytes.extend_from_slice(&mov_zero.to_le_bytes());
3267
3268                Ok(bytes) // Total: 38 bytes
3269            }
3270
3271            // I64ShrS: 64-bit arithmetic shift right with branch for n<32 vs n>=32
3272            ArmOp::I64ShrS {
3273                rd_lo,
3274                rd_hi,
3275                rn_lo,
3276                rn_hi,
3277                rm_lo,
3278                rm_hi,
3279            } => {
3280                let rd_lo_bits = reg_to_bits(rd_lo);
3281                let rd_hi_bits = reg_to_bits(rd_hi);
3282                let rn_lo_bits = reg_to_bits(rn_lo);
3283                let rn_hi_bits = reg_to_bits(rn_hi);
3284                let rm_lo_bits = reg_to_bits(rm_lo);
3285                let rm_hi_bits = reg_to_bits(rm_hi); // temp
3286                let mut bytes = Vec::new();
3287
3288                // AND.W rm_lo, rm_lo, #63
3289                let hw1: u16 = (0xF000 | rm_lo_bits) as u16;
3290                let hw2: u16 = ((rm_lo_bits << 8) | 0x3F) as u16;
3291                bytes.extend_from_slice(&hw1.to_le_bytes());
3292                bytes.extend_from_slice(&hw2.to_le_bytes());
3293
3294                // SUBS.W rm_hi, rm_lo, #32
3295                let hw1: u16 = (0xF1B0 | rm_lo_bits) as u16;
3296                let hw2: u16 = ((rm_hi_bits << 8) | 0x20) as u16;
3297                bytes.extend_from_slice(&hw1.to_le_bytes());
3298                bytes.extend_from_slice(&hw2.to_le_bytes());
3299
3300                // BPL .large (+10 halfwords)
3301                let bpl: u16 = 0xD50A;
3302                bytes.extend_from_slice(&bpl.to_le_bytes());
3303
3304                // --- Small shift (n < 32) ---
3305                // RSB.W rm_hi, rm_lo, #32
3306                let hw1: u16 = (0xF1C0 | rm_lo_bits) as u16;
3307                let hw2: u16 = ((rm_hi_bits << 8) | 0x20) as u16;
3308                bytes.extend_from_slice(&hw1.to_le_bytes());
3309                bytes.extend_from_slice(&hw2.to_le_bytes());
3310
3311                // LSL.W rm_hi, rn_hi, rm_hi  (rm_hi = hi << (32-n), bits flowing to lo)
3312                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3313                let hw2: u16 = (0xF000 | (rm_hi_bits << 8) | rm_hi_bits) as u16;
3314                bytes.extend_from_slice(&hw1.to_le_bytes());
3315                bytes.extend_from_slice(&hw2.to_le_bytes());
3316
3317                // LSR.W rd_lo, rn_lo, rm_lo  (lo >>= n, logical for lo word)
3318                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3319                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | rm_lo_bits) as u16;
3320                bytes.extend_from_slice(&hw1.to_le_bytes());
3321                bytes.extend_from_slice(&hw2.to_le_bytes());
3322
3323                // ORR.W rd_lo, rd_lo, rm_hi  (lo |= overflow from hi)
3324                let hw1: u16 = (0xEA40 | rd_lo_bits) as u16;
3325                let hw2: u16 = ((rd_lo_bits << 8) | rm_hi_bits) as u16;
3326                bytes.extend_from_slice(&hw1.to_le_bytes());
3327                bytes.extend_from_slice(&hw2.to_le_bytes());
3328
3329                // ASR.W rd_hi, rn_hi, rm_lo  (hi >>= n, arithmetic/sign-extending)
3330                let hw1: u16 = (0xFA40 | rn_hi_bits) as u16;
3331                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | rm_lo_bits) as u16;
3332                bytes.extend_from_slice(&hw1.to_le_bytes());
3333                bytes.extend_from_slice(&hw2.to_le_bytes());
3334
3335                // B .done (+3 halfwords, large shift is 8 bytes)
3336                let b_done: u16 = 0xE003;
3337                bytes.extend_from_slice(&b_done.to_le_bytes());
3338
3339                // --- Large shift (n >= 32) ---
3340                // ASR.W rd_lo, rn_hi, rm_hi  (lo = hi >>> (n-32))
3341                let hw1: u16 = (0xFA40 | rn_hi_bits) as u16;
3342                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | rm_hi_bits) as u16;
3343                bytes.extend_from_slice(&hw1.to_le_bytes());
3344                bytes.extend_from_slice(&hw2.to_le_bytes());
3345
3346                // ASR.W rd_hi, rn_hi, #31  (hi = sign extension, all 0s or all 1s)
3347                // Thumb-2 ASR immediate: hw1=0xEA4F, hw2=imm3:Rd:imm2:10:Rm
3348                // imm5=31=11111 → imm3=111, imm2=11
3349                let hw1: u16 = 0xEA4F;
3350                let hw2: u16 = (0x7000 | (rd_hi_bits << 8) | 0x00E0 | rn_hi_bits) as u16;
3351                bytes.extend_from_slice(&hw1.to_le_bytes());
3352                bytes.extend_from_slice(&hw2.to_le_bytes());
3353
3354                Ok(bytes) // Total: 40 bytes
3355            }
3356
3357            // I64Rotl: 64-bit rotate left
3358            // For n < 32: new_hi = (hi << n) | (lo >> (32-n)), new_lo = (lo << n) | (hi >> (32-n))
3359            // For n >= 32: same formula but with lo/hi conceptually swapped, shift by (n-32)
3360            // Uses R4 (saved/restored) and R12 as scratch
3361            ArmOp::I64Rotl {
3362                rdlo,
3363                rdhi,
3364                rnlo,
3365                rnhi,
3366                shift,
3367            } => {
3368                let rd_lo_bits = reg_to_bits(rdlo);
3369                let rd_hi_bits = reg_to_bits(rdhi);
3370                let rn_lo_bits = reg_to_bits(rnlo);
3371                let rn_hi_bits = reg_to_bits(rnhi);
3372                let shift_bits = reg_to_bits(shift);
3373                let r12: u32 = 12; // IP scratch
3374                let r3: u32 = 3; // Scratch (high word of shift amount, unused)
3375                let r4: u32 = 4; // Scratch (saved/restored)
3376                let mut bytes = Vec::new();
3377
3378                // PUSH {R4}
3379                bytes.extend_from_slice(&0xB410u16.to_le_bytes());
3380
3381                // AND.W shift, shift, #63 (mask to 6 bits)
3382                let hw1: u16 = (0xF000 | shift_bits) as u16;
3383                let hw2: u16 = ((shift_bits << 8) | 0x3F) as u16;
3384                bytes.extend_from_slice(&hw1.to_le_bytes());
3385                bytes.extend_from_slice(&hw2.to_le_bytes());
3386
3387                // SUBS.W R3, shift, #32 (R3 = n-32, sets flags)
3388                let hw1: u16 = (0xF1B0 | shift_bits) as u16;
3389                let hw2: u16 = ((r3 << 8) | 0x20) as u16;
3390                bytes.extend_from_slice(&hw1.to_le_bytes());
3391                bytes.extend_from_slice(&hw2.to_le_bytes());
3392
3393                // BPL .large (branch if n >= 32, offset = +14 halfwords)
3394                let bpl: u16 = 0xD50E;
3395                bytes.extend_from_slice(&bpl.to_le_bytes());
3396
3397                // === Small rotation (n < 32) ===
3398                // RSB.W R3, shift, #32 (R3 = 32-n)
3399                let hw1: u16 = (0xF1C0 | shift_bits) as u16;
3400                let hw2: u16 = ((r3 << 8) | 0x20) as u16;
3401                bytes.extend_from_slice(&hw1.to_le_bytes());
3402                bytes.extend_from_slice(&hw2.to_le_bytes());
3403
3404                // LSR.W R4, rn_lo, R3 (R4 = lo >> (32-n), will go to new_hi)
3405                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3406                let hw2: u16 = (0xF000 | (r4 << 8) | r3) as u16;
3407                bytes.extend_from_slice(&hw1.to_le_bytes());
3408                bytes.extend_from_slice(&hw2.to_le_bytes());
3409
3410                // LSR.W R12, rn_hi, R3 (R12 = hi >> (32-n), will go to new_lo)
3411                let hw1: u16 = (0xFA20 | rn_hi_bits) as u16;
3412                let hw2: u16 = (0xF000 | (r12 << 8) | r3) as u16;
3413                bytes.extend_from_slice(&hw1.to_le_bytes());
3414                bytes.extend_from_slice(&hw2.to_le_bytes());
3415
3416                // LSL.W rd_hi, rn_hi, shift (rd_hi = hi << n)
3417                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3418                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | shift_bits) as u16;
3419                bytes.extend_from_slice(&hw1.to_le_bytes());
3420                bytes.extend_from_slice(&hw2.to_le_bytes());
3421
3422                // ORR.W rd_hi, rd_hi, R4 (rd_hi = (hi << n) | (lo >> (32-n)))
3423                let hw1: u16 = (0xEA40 | rd_hi_bits) as u16;
3424                let hw2: u16 = ((rd_hi_bits << 8) | r4) as u16;
3425                bytes.extend_from_slice(&hw1.to_le_bytes());
3426                bytes.extend_from_slice(&hw2.to_le_bytes());
3427
3428                // LSL.W rd_lo, rn_lo, shift (rd_lo = lo << n)
3429                let hw1: u16 = (0xFA00 | rn_lo_bits) as u16;
3430                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | shift_bits) as u16;
3431                bytes.extend_from_slice(&hw1.to_le_bytes());
3432                bytes.extend_from_slice(&hw2.to_le_bytes());
3433
3434                // ORR.W rd_lo, rd_lo, R12 (rd_lo = (lo << n) | (hi >> (32-n)))
3435                let hw1: u16 = (0xEA40 | rd_lo_bits) as u16;
3436                let hw2: u16 = ((rd_lo_bits << 8) | r12) as u16;
3437                bytes.extend_from_slice(&hw1.to_le_bytes());
3438                bytes.extend_from_slice(&hw2.to_le_bytes());
3439
3440                // B .done (skip large block, offset = +14 halfwords)
3441                let b_done: u16 = 0xE00E;
3442                bytes.extend_from_slice(&b_done.to_le_bytes());
3443
3444                // === Large rotation (n >= 32) ===
3445                // R3 already has n-32 from the SUBS
3446                // RSB.W R4, R3, #32 (R4 = 32-(n-32) = 64-n)
3447                let hw1: u16 = (0xF1C0 | r3) as u16;
3448                let hw2: u16 = ((r4 << 8) | 0x20) as u16;
3449                bytes.extend_from_slice(&hw1.to_le_bytes());
3450                bytes.extend_from_slice(&hw2.to_le_bytes());
3451
3452                // LSR.W R12, rn_hi, R4 (R12 = hi >> (64-n), goes to new_hi low bits)
3453                let hw1: u16 = (0xFA20 | rn_hi_bits) as u16;
3454                let hw2: u16 = (0xF000 | (r12 << 8) | r4) as u16;
3455                bytes.extend_from_slice(&hw1.to_le_bytes());
3456                bytes.extend_from_slice(&hw2.to_le_bytes());
3457
3458                // LSR.W R4, rn_lo, R4 (R4 = lo >> (64-n), goes to new_lo low bits)
3459                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3460                let hw2: u16 = (0xF000 | (r4 << 8) | r4) as u16;
3461                bytes.extend_from_slice(&hw1.to_le_bytes());
3462                bytes.extend_from_slice(&hw2.to_le_bytes());
3463
3464                // LSL.W shift, rn_lo, R3 (shift = lo << (n-32), new_hi high bits)
3465                let hw1: u16 = (0xFA00 | rn_lo_bits) as u16;
3466                let hw2: u16 = (0xF000 | (shift_bits << 8) | r3) as u16;
3467                bytes.extend_from_slice(&hw1.to_le_bytes());
3468                bytes.extend_from_slice(&hw2.to_le_bytes());
3469
3470                // ORR.W shift, shift, R12 (shift = (lo << (n-32)) | (hi >> (64-n)) = new_hi)
3471                let hw1: u16 = (0xEA40 | shift_bits) as u16;
3472                let hw2: u16 = ((shift_bits << 8) | r12) as u16;
3473                bytes.extend_from_slice(&hw1.to_le_bytes());
3474                bytes.extend_from_slice(&hw2.to_le_bytes());
3475
3476                // LSL.W rd_lo, rn_hi, R3 (rd_lo = hi << (n-32), new_lo high bits)
3477                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3478                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | r3) as u16;
3479                bytes.extend_from_slice(&hw1.to_le_bytes());
3480                bytes.extend_from_slice(&hw2.to_le_bytes());
3481
3482                // ORR.W rd_lo, rd_lo, R4 (rd_lo = (hi << (n-32)) | (lo >> (64-n)) = new_lo)
3483                let hw1: u16 = (0xEA40 | rd_lo_bits) as u16;
3484                let hw2: u16 = ((rd_lo_bits << 8) | r4) as u16;
3485                bytes.extend_from_slice(&hw1.to_le_bytes());
3486                bytes.extend_from_slice(&hw2.to_le_bytes());
3487
3488                // MOV rd_hi, shift (rd_hi = new_hi)
3489                let d_bit = (rd_hi_bits >> 3) & 1;
3490                let mov_instr: u16 =
3491                    (0x4600 | (d_bit << 7) | (shift_bits << 3) | (rd_hi_bits & 0x7)) as u16;
3492                bytes.extend_from_slice(&mov_instr.to_le_bytes());
3493
3494                // POP {R4}
3495                bytes.extend_from_slice(&0xBC10u16.to_le_bytes());
3496
3497                Ok(bytes) // Total: 74 bytes
3498            }
3499
3500            // I64Rotr: 64-bit rotate right
3501            // rotr(x, n) = rotl(x, 64-n)
3502            // For n < 32: new_lo = (lo >> n) | (hi << (32-n)), new_hi = (hi >> n) | (lo << (32-n))
3503            // For n >= 32: same formula but with lo/hi swapped, shift by (n-32)
3504            ArmOp::I64Rotr {
3505                rdlo,
3506                rdhi,
3507                rnlo,
3508                rnhi,
3509                shift,
3510            } => {
3511                let rd_lo_bits = reg_to_bits(rdlo);
3512                let rd_hi_bits = reg_to_bits(rdhi);
3513                let rn_lo_bits = reg_to_bits(rnlo);
3514                let rn_hi_bits = reg_to_bits(rnhi);
3515                let shift_bits = reg_to_bits(shift);
3516                let r12: u32 = 12;
3517                let r3: u32 = 3;
3518                let r4: u32 = 4;
3519                let mut bytes = Vec::new();
3520
3521                // PUSH {R4}
3522                bytes.extend_from_slice(&0xB410u16.to_le_bytes());
3523
3524                // AND.W shift, shift, #63
3525                let hw1: u16 = (0xF000 | shift_bits) as u16;
3526                let hw2: u16 = ((shift_bits << 8) | 0x3F) as u16;
3527                bytes.extend_from_slice(&hw1.to_le_bytes());
3528                bytes.extend_from_slice(&hw2.to_le_bytes());
3529
3530                // SUBS.W R3, shift, #32
3531                let hw1: u16 = (0xF1B0 | shift_bits) as u16;
3532                let hw2: u16 = ((r3 << 8) | 0x20) as u16;
3533                bytes.extend_from_slice(&hw1.to_le_bytes());
3534                bytes.extend_from_slice(&hw2.to_le_bytes());
3535
3536                // BPL .large (+14 halfwords)
3537                let bpl: u16 = 0xD50E;
3538                bytes.extend_from_slice(&bpl.to_le_bytes());
3539
3540                // === Small rotation (n < 32) ===
3541                // RSB.W R3, shift, #32 (R3 = 32-n)
3542                let hw1: u16 = (0xF1C0 | shift_bits) as u16;
3543                let hw2: u16 = ((r3 << 8) | 0x20) as u16;
3544                bytes.extend_from_slice(&hw1.to_le_bytes());
3545                bytes.extend_from_slice(&hw2.to_le_bytes());
3546
3547                // LSL.W R4, rn_hi, R3 (R4 = hi << (32-n), will go to new_lo)
3548                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3549                let hw2: u16 = (0xF000 | (r4 << 8) | r3) as u16;
3550                bytes.extend_from_slice(&hw1.to_le_bytes());
3551                bytes.extend_from_slice(&hw2.to_le_bytes());
3552
3553                // LSL.W R12, rn_lo, R3 (R12 = lo << (32-n), will go to new_hi)
3554                let hw1: u16 = (0xFA00 | rn_lo_bits) as u16;
3555                let hw2: u16 = (0xF000 | (r12 << 8) | r3) as u16;
3556                bytes.extend_from_slice(&hw1.to_le_bytes());
3557                bytes.extend_from_slice(&hw2.to_le_bytes());
3558
3559                // LSR.W rd_lo, rn_lo, shift (rd_lo = lo >> n)
3560                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3561                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | shift_bits) as u16;
3562                bytes.extend_from_slice(&hw1.to_le_bytes());
3563                bytes.extend_from_slice(&hw2.to_le_bytes());
3564
3565                // ORR.W rd_lo, rd_lo, R4 (rd_lo = (lo >> n) | (hi << (32-n)))
3566                let hw1: u16 = (0xEA40 | rd_lo_bits) as u16;
3567                let hw2: u16 = ((rd_lo_bits << 8) | r4) as u16;
3568                bytes.extend_from_slice(&hw1.to_le_bytes());
3569                bytes.extend_from_slice(&hw2.to_le_bytes());
3570
3571                // LSR.W rd_hi, rn_hi, shift (rd_hi = hi >> n)
3572                let hw1: u16 = (0xFA20 | rn_hi_bits) as u16;
3573                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | shift_bits) as u16;
3574                bytes.extend_from_slice(&hw1.to_le_bytes());
3575                bytes.extend_from_slice(&hw2.to_le_bytes());
3576
3577                // ORR.W rd_hi, rd_hi, R12 (rd_hi = (hi >> n) | (lo << (32-n)))
3578                let hw1: u16 = (0xEA40 | rd_hi_bits) as u16;
3579                let hw2: u16 = ((rd_hi_bits << 8) | r12) as u16;
3580                bytes.extend_from_slice(&hw1.to_le_bytes());
3581                bytes.extend_from_slice(&hw2.to_le_bytes());
3582
3583                // B .done (+14 halfwords)
3584                let b_done: u16 = 0xE00E;
3585                bytes.extend_from_slice(&b_done.to_le_bytes());
3586
3587                // === Large rotation (n >= 32) ===
3588                // RSB.W R4, R3, #32 (R4 = 64-n)
3589                let hw1: u16 = (0xF1C0 | r3) as u16;
3590                let hw2: u16 = ((r4 << 8) | 0x20) as u16;
3591                bytes.extend_from_slice(&hw1.to_le_bytes());
3592                bytes.extend_from_slice(&hw2.to_le_bytes());
3593
3594                // LSL.W R12, rn_lo, R4 (R12 = lo << (64-n), goes to new_lo low bits)
3595                let hw1: u16 = (0xFA00 | rn_lo_bits) as u16;
3596                let hw2: u16 = (0xF000 | (r12 << 8) | r4) as u16;
3597                bytes.extend_from_slice(&hw1.to_le_bytes());
3598                bytes.extend_from_slice(&hw2.to_le_bytes());
3599
3600                // LSL.W R4, rn_hi, R4 (R4 = hi << (64-n), goes to new_hi low bits)
3601                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3602                let hw2: u16 = (0xF000 | (r4 << 8) | r4) as u16;
3603                bytes.extend_from_slice(&hw1.to_le_bytes());
3604                bytes.extend_from_slice(&hw2.to_le_bytes());
3605
3606                // LSR.W shift, rn_hi, R3 (shift = hi >> (n-32), new_lo high bits)
3607                let hw1: u16 = (0xFA20 | rn_hi_bits) as u16;
3608                let hw2: u16 = (0xF000 | (shift_bits << 8) | r3) as u16;
3609                bytes.extend_from_slice(&hw1.to_le_bytes());
3610                bytes.extend_from_slice(&hw2.to_le_bytes());
3611
3612                // ORR.W shift, shift, R12 (shift = (hi >> (n-32)) | (lo << (64-n)) = new_lo)
3613                let hw1: u16 = (0xEA40 | shift_bits) as u16;
3614                let hw2: u16 = ((shift_bits << 8) | r12) as u16;
3615                bytes.extend_from_slice(&hw1.to_le_bytes());
3616                bytes.extend_from_slice(&hw2.to_le_bytes());
3617
3618                // LSR.W rd_hi, rn_lo, R3 (rd_hi = lo >> (n-32), new_hi high bits)
3619                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3620                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | r3) as u16;
3621                bytes.extend_from_slice(&hw1.to_le_bytes());
3622                bytes.extend_from_slice(&hw2.to_le_bytes());
3623
3624                // ORR.W rd_hi, rd_hi, R4 (rd_hi = (lo >> (n-32)) | (hi << (64-n)) = new_hi)
3625                let hw1: u16 = (0xEA40 | rd_hi_bits) as u16;
3626                let hw2: u16 = ((rd_hi_bits << 8) | r4) as u16;
3627                bytes.extend_from_slice(&hw1.to_le_bytes());
3628                bytes.extend_from_slice(&hw2.to_le_bytes());
3629
3630                // MOV rd_lo, shift (rd_lo = new_lo)
3631                let d_bit = (rd_lo_bits >> 3) & 1;
3632                let mov_instr: u16 =
3633                    (0x4600 | (d_bit << 7) | (shift_bits << 3) | (rd_lo_bits & 0x7)) as u16;
3634                bytes.extend_from_slice(&mov_instr.to_le_bytes());
3635
3636                // POP {R4}
3637                bytes.extend_from_slice(&0xBC10u16.to_le_bytes());
3638
3639                Ok(bytes) // Total: 74 bytes
3640            }
3641
3642            // I64Clz: Count leading zeros in 64-bit value
3643            // If hi != 0: result = CLZ(hi)
3644            // If hi == 0: result = 32 + CLZ(lo)
3645            //
3646            // Layout (using CMP+BNE approach for consistency):
3647            // 0: CMP.W rnhi, #0 (4 bytes)
3648            // 4: BEQ .hi_zero (2 bytes) - branch forward to offset 14
3649            // 6: CLZ.W rd, rnhi (4 bytes)
3650            // 10: B .done (2 bytes) - branch forward to offset 22
3651            // 12: NOP (2 bytes) - padding for alignment
3652            // 14: .hi_zero: CLZ.W rd, rnlo (4 bytes)
3653            // 18: ADD.W rd, rd, #32 (4 bytes)
3654            // 22: .done
3655            ArmOp::I64Clz { rd, rnlo, rnhi } => {
3656                let rd_bits = reg_to_bits(rd);
3657                let rn_lo_bits = reg_to_bits(rnlo);
3658                let rn_hi_bits = reg_to_bits(rnhi);
3659                let mut bytes = Vec::new();
3660
3661                // CMP.W rnhi, #0 (4 bytes at offset 0)
3662                let hw1: u16 = (0xF1B0 | rn_hi_bits) as u16;
3663                let hw2: u16 = 0x0F00;
3664                bytes.extend_from_slice(&hw1.to_le_bytes());
3665                bytes.extend_from_slice(&hw2.to_le_bytes());
3666
3667                // BEQ .hi_zero (2 bytes at offset 4)
3668                // PC = 4 + 4 = 8, target = 14, offset = 6, imm8 = 3
3669                let beq: u16 = 0xD003;
3670                bytes.extend_from_slice(&beq.to_le_bytes());
3671
3672                // CLZ.W rd, rnhi (4 bytes at offset 6)
3673                // CLZ T1: hw1 = 0xFAB<Rm>, hw2 = 0xF<Rd>8<Rm>
3674                let hw1: u16 = (0xFAB0 | rn_hi_bits) as u16;
3675                let hw2: u16 = (0xF080 | (rd_bits << 8) | rn_hi_bits) as u16;
3676                bytes.extend_from_slice(&hw1.to_le_bytes());
3677                bytes.extend_from_slice(&hw2.to_le_bytes());
3678
3679                // B .done (2 bytes at offset 10)
3680                // PC = 10 + 4 = 14, target = 22, offset = 8, imm11 = 4
3681                let b_done: u16 = 0xE004;
3682                bytes.extend_from_slice(&b_done.to_le_bytes());
3683
3684                // NOP (2 bytes at offset 12) - padding
3685                bytes.extend_from_slice(&0xBF00u16.to_le_bytes());
3686
3687                // .hi_zero: (offset 14)
3688                // CLZ.W rd, rnlo (4 bytes)
3689                // CLZ T1: hw1 = 0xFAB<Rm>, hw2 = 0xF<Rd>8<Rm>
3690                let hw1: u16 = (0xFAB0 | rn_lo_bits) as u16;
3691                let hw2: u16 = (0xF080 | (rd_bits << 8) | rn_lo_bits) as u16;
3692                bytes.extend_from_slice(&hw1.to_le_bytes());
3693                bytes.extend_from_slice(&hw2.to_le_bytes());
3694
3695                // ADD.W rd, rd, #32 (4 bytes at offset 18)
3696                let hw1: u16 = (0xF100 | rd_bits) as u16;
3697                let hw2: u16 = ((rd_bits << 8) | 0x20) as u16;
3698                bytes.extend_from_slice(&hw1.to_le_bytes());
3699                bytes.extend_from_slice(&hw2.to_le_bytes());
3700
3701                // .done: (offset 22)
3702                // i64.clz returns i64, so clear high word: MOV rnhi, #0 (2 bytes)
3703                // MOVS Rn, #0: 0010 0 Rn 00000000
3704                let mov0: u16 = (0x2000 | (rn_hi_bits << 8)) as u16;
3705                bytes.extend_from_slice(&mov0.to_le_bytes());
3706
3707                Ok(bytes)
3708            }
3709
3710            // I64Ctz: Count trailing zeros in 64-bit value
3711            // If lo != 0: result = CTZ(lo) = CLZ(RBIT(lo))
3712            // If lo == 0: result = 32 + CTZ(hi) = 32 + CLZ(RBIT(hi))
3713            //
3714            // Layout:
3715            // 0: CMP.W rnlo, #0 (4 bytes)
3716            // 4: BEQ .lo_zero (2 bytes) - branch to offset 18
3717            // 6: RBIT.W rd, rnlo (4 bytes)
3718            // 10: CLZ.W rd, rd (4 bytes)
3719            // 14: B .done (2 bytes) - branch to offset 30
3720            // 16: NOP (2 bytes) - padding
3721            // 18: .lo_zero: RBIT.W rd, rnhi (4 bytes)
3722            // 22: CLZ.W rd, rd (4 bytes)
3723            // 26: ADD.W rd, rd, #32 (4 bytes)
3724            // 30: .done
3725            ArmOp::I64Ctz { rd, rnlo, rnhi } => {
3726                let rd_bits = reg_to_bits(rd);
3727                let rn_lo_bits = reg_to_bits(rnlo);
3728                let rn_hi_bits = reg_to_bits(rnhi);
3729                let mut bytes = Vec::new();
3730
3731                // CMP.W rnlo, #0 (4 bytes at offset 0)
3732                let hw1: u16 = (0xF1B0 | rn_lo_bits) as u16;
3733                let hw2: u16 = 0x0F00;
3734                bytes.extend_from_slice(&hw1.to_le_bytes());
3735                bytes.extend_from_slice(&hw2.to_le_bytes());
3736
3737                // BEQ .lo_zero (2 bytes at offset 4)
3738                // PC = 4 + 4 = 8, target = 18, offset = 10, imm8 = 5
3739                let beq: u16 = 0xD005;
3740                bytes.extend_from_slice(&beq.to_le_bytes());
3741
3742                // RBIT.W rd, rnlo (4 bytes at offset 6)
3743                // RBIT T1: hw1 = 0xFA9<Rm>, hw2 = 0xF<Rd>A<Rm>
3744                let hw1: u16 = (0xFA90 | rn_lo_bits) as u16;
3745                let hw2: u16 = (0xF0A0 | (rd_bits << 8) | rn_lo_bits) as u16;
3746                bytes.extend_from_slice(&hw1.to_le_bytes());
3747                bytes.extend_from_slice(&hw2.to_le_bytes());
3748
3749                // CLZ.W rd, rd (4 bytes at offset 10)
3750                // CLZ T1: hw1 = 0xFAB<Rm>, hw2 = 0xF<Rd>8<Rm>
3751                let hw1: u16 = (0xFAB0 | rd_bits) as u16;
3752                let hw2: u16 = (0xF080 | (rd_bits << 8) | rd_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 14)
3757                // PC = 14 + 4 = 18, target = 30, offset = 12, imm11 = 6
3758                let b_done: u16 = 0xE006;
3759                bytes.extend_from_slice(&b_done.to_le_bytes());
3760
3761                // NOP (2 bytes at offset 16) - padding
3762                bytes.extend_from_slice(&0xBF00u16.to_le_bytes());
3763
3764                // .lo_zero: (offset 18)
3765                // RBIT.W rd, rnhi (4 bytes)
3766                // RBIT T1: hw1 = 0xFA9<Rm>, hw2 = 0xF<Rd>A<Rm>
3767                let hw1: u16 = (0xFA90 | rn_hi_bits) as u16;
3768                let hw2: u16 = (0xF0A0 | (rd_bits << 8) | rn_hi_bits) as u16;
3769                bytes.extend_from_slice(&hw1.to_le_bytes());
3770                bytes.extend_from_slice(&hw2.to_le_bytes());
3771
3772                // CLZ.W rd, rd (4 bytes at offset 22)
3773                // CLZ T1: hw1 = 0xFAB<Rm>, hw2 = 0xF<Rd>8<Rm>
3774                let hw1: u16 = (0xFAB0 | rd_bits) as u16;
3775                let hw2: u16 = (0xF080 | (rd_bits << 8) | rd_bits) as u16;
3776                bytes.extend_from_slice(&hw1.to_le_bytes());
3777                bytes.extend_from_slice(&hw2.to_le_bytes());
3778
3779                // ADD.W rd, rd, #32 (4 bytes at offset 26)
3780                let hw1: u16 = (0xF100 | rd_bits) as u16;
3781                let hw2: u16 = ((rd_bits << 8) | 0x20) as u16;
3782                bytes.extend_from_slice(&hw1.to_le_bytes());
3783                bytes.extend_from_slice(&hw2.to_le_bytes());
3784
3785                // .done: (offset 30)
3786                // i64.ctz returns i64, so clear high word: MOV rnhi, #0 (2 bytes)
3787                let mov0: u16 = (0x2000 | (rn_hi_bits << 8)) as u16;
3788                bytes.extend_from_slice(&mov0.to_le_bytes());
3789
3790                Ok(bytes)
3791            }
3792
3793            // I64Popcnt: Population count of 64-bit value
3794            // result = POPCNT(lo) + POPCNT(hi)
3795            // Using SIMD-style parallel bit counting algorithm
3796            ArmOp::I64Popcnt { rd, rnlo, rnhi } => {
3797                let rd_bits = reg_to_bits(rd);
3798                let rn_lo_bits = reg_to_bits(rnlo);
3799                let rn_hi_bits = reg_to_bits(rnhi);
3800                let r12: u32 = 12; // IP scratch
3801                let r3: u32 = 3; // Scratch for hi popcnt result
3802                let mut bytes = Vec::new();
3803
3804                // PUSH {R3, R4, R5} - save scratch registers
3805                bytes.extend_from_slice(&0xB438u16.to_le_bytes());
3806
3807                // Strategy: compute popcnt(lo) -> R4, popcnt(hi) -> R5, add them -> rd
3808                // Using lookup table approach for each byte would be too large
3809                // Using shift-and-add approach instead
3810
3811                // For simplicity and correctness, use the efficient parallel algorithm
3812                // but implement it as a series of inline operations
3813
3814                // MOV R4, rnlo
3815                let d_bit: u32 = 0; // R4 < 8, so high bit is 0
3816                let mov: u16 = (0x4600 | (d_bit << 7) | (rn_lo_bits << 3) | (4 & 0x7)) as u16;
3817                bytes.extend_from_slice(&mov.to_le_bytes());
3818
3819                // MOV R5, rnhi
3820                let d_bit: u32 = 0; // R5 < 8, so high bit is 0
3821                let mov: u16 = (0x4600 | (d_bit << 7) | (rn_hi_bits << 3) | (5 & 0x7)) as u16;
3822                bytes.extend_from_slice(&mov.to_le_bytes());
3823
3824                // --- POPCNT for R4 (lo word) ---
3825                // Step 1: x = x - ((x >> 1) & 0x55555555)
3826                // LSR.W R12, R4, #1
3827                let hw1: u16 = 0xEA4F;
3828                let hw2: u16 = ((r12 << 8) | 0x50 | 4) as u16;
3829                bytes.extend_from_slice(&hw1.to_le_bytes());
3830                bytes.extend_from_slice(&hw2.to_le_bytes());
3831
3832                // Load 0x55555555 into R3 using MOVW/MOVT
3833                // MOVW R3, #0x5555
3834                bytes.extend_from_slice(&0xF245u16.to_le_bytes());
3835                bytes.extend_from_slice(&0x5355u16.to_le_bytes());
3836                // MOVT R3, #0x5555
3837                bytes.extend_from_slice(&0xF2C5u16.to_le_bytes());
3838                bytes.extend_from_slice(&0x5355u16.to_le_bytes());
3839
3840                // AND.W R12, R12, R3
3841                let hw1: u16 = (0xEA00 | r12) as u16;
3842                let hw2: u16 = ((r12 << 8) | r3) as u16;
3843                bytes.extend_from_slice(&hw1.to_le_bytes());
3844                bytes.extend_from_slice(&hw2.to_le_bytes());
3845
3846                // SUB.W R4, R4, R12
3847                let hw1: u16 = (0xEBA0 | 4) as u16;
3848                let hw2: u16 = ((4 << 8) | r12) as u16;
3849                bytes.extend_from_slice(&hw1.to_le_bytes());
3850                bytes.extend_from_slice(&hw2.to_le_bytes());
3851
3852                // Step 2: x = (x & 0x33333333) + ((x >> 2) & 0x33333333)
3853                // Load 0x33333333 into R3
3854                // MOVW R3, #0x3333
3855                bytes.extend_from_slice(&0xF243u16.to_le_bytes());
3856                bytes.extend_from_slice(&0x3333u16.to_le_bytes());
3857                // MOVT R3, #0x3333
3858                bytes.extend_from_slice(&0xF2C3u16.to_le_bytes());
3859                bytes.extend_from_slice(&0x3333u16.to_le_bytes());
3860
3861                // AND.W R12, R4, R3
3862                let hw1: u16 = (0xEA00 | 4) as u16;
3863                let hw2: u16 = ((r12 << 8) | r3) as u16;
3864                bytes.extend_from_slice(&hw1.to_le_bytes());
3865                bytes.extend_from_slice(&hw2.to_le_bytes());
3866
3867                // LSR.W R4, R4, #2
3868                let hw1: u16 = 0xEA4F;
3869                let hw2: u16 = ((4 << 8) | 0x90 | 4) as u16;
3870                bytes.extend_from_slice(&hw1.to_le_bytes());
3871                bytes.extend_from_slice(&hw2.to_le_bytes());
3872
3873                // AND.W R4, R4, R3
3874                let hw1: u16 = (0xEA00 | 4) as u16;
3875                let hw2: u16 = ((4 << 8) | r3) as u16;
3876                bytes.extend_from_slice(&hw1.to_le_bytes());
3877                bytes.extend_from_slice(&hw2.to_le_bytes());
3878
3879                // ADD.W R4, R4, R12
3880                let hw1: u16 = (0xEB00 | 4) as u16;
3881                let hw2: u16 = ((4 << 8) | r12) as u16;
3882                bytes.extend_from_slice(&hw1.to_le_bytes());
3883                bytes.extend_from_slice(&hw2.to_le_bytes());
3884
3885                // Step 3: x = (x + (x >> 4)) & 0x0F0F0F0F
3886                // LSR.W R12, R4, #4
3887                // hw2 = (imm3 << 12) | (Rd << 8) | (imm2 << 6) | (type << 4) | Rm
3888                // imm5=4=00100 → imm3=1, imm2=0, type=01(LSR)
3889                let hw1: u16 = 0xEA4F;
3890                let hw2: u16 = (0x1000 | (r12 << 8) | 0x10 | 4) as u16;
3891                bytes.extend_from_slice(&hw1.to_le_bytes());
3892                bytes.extend_from_slice(&hw2.to_le_bytes());
3893
3894                // ADD.W R4, R4, R12
3895                let hw1: u16 = (0xEB00 | 4) as u16;
3896                let hw2: u16 = ((4 << 8) | r12) as u16;
3897                bytes.extend_from_slice(&hw1.to_le_bytes());
3898                bytes.extend_from_slice(&hw2.to_le_bytes());
3899
3900                // Load 0x0F0F0F0F into R3
3901                // MOVW R3, #0x0F0F (imm4=0, i=1, imm3=7, imm8=0x0F)
3902                // hw1 = 11110 1 10 0100 0000 = 0xF640
3903                // hw2 = 0 111 0011 00001111 = 0x730F
3904                bytes.extend_from_slice(&0xF640u16.to_le_bytes());
3905                bytes.extend_from_slice(&0x730Fu16.to_le_bytes());
3906                // MOVT R3, #0x0F0F
3907                bytes.extend_from_slice(&0xF6C0u16.to_le_bytes());
3908                bytes.extend_from_slice(&0x730Fu16.to_le_bytes());
3909
3910                // AND.W R4, R4, R3
3911                let hw1: u16 = (0xEA00 | 4) as u16;
3912                let hw2: u16 = ((4 << 8) | r3) as u16;
3913                bytes.extend_from_slice(&hw1.to_le_bytes());
3914                bytes.extend_from_slice(&hw2.to_le_bytes());
3915
3916                // Step 4: x = x * 0x01010101 >> 24
3917                // Load 0x01010101 into R3
3918                // MOVW R3, #0x0101
3919                bytes.extend_from_slice(&0xF240u16.to_le_bytes());
3920                bytes.extend_from_slice(&0x1301u16.to_le_bytes());
3921                // MOVT R3, #0x0101
3922                bytes.extend_from_slice(&0xF2C0u16.to_le_bytes());
3923                bytes.extend_from_slice(&0x1301u16.to_le_bytes());
3924
3925                // MUL R4, R4, R3
3926                // MUL T2: hw1 = 0xFB00|Rn, hw2 = 0xF000|(Rd<<8)|Rm
3927                let hw1: u16 = (0xFB00 | 4) as u16;
3928                let hw2: u16 = (0xF000 | (4 << 8) | r3) as u16;
3929                bytes.extend_from_slice(&hw1.to_le_bytes());
3930                bytes.extend_from_slice(&hw2.to_le_bytes());
3931
3932                // LSR.W R4, R4, #24
3933                // imm5=24=11000 → imm3=6, imm2=0, type=01(LSR)
3934                let hw1: u16 = 0xEA4F;
3935                let hw2: u16 = (0x6000 | (4 << 8) | 0x10 | 4) as u16;
3936                bytes.extend_from_slice(&hw1.to_le_bytes());
3937                bytes.extend_from_slice(&hw2.to_le_bytes());
3938
3939                // --- POPCNT for R5 (hi word) - same algorithm ---
3940                // Step 1
3941                let hw1: u16 = 0xEA4F;
3942                let hw2: u16 = ((r12 << 8) | 0x50 | 5) as u16;
3943                bytes.extend_from_slice(&hw1.to_le_bytes());
3944                bytes.extend_from_slice(&hw2.to_le_bytes());
3945
3946                // Load 0x55555555 into R3
3947                bytes.extend_from_slice(&0xF245u16.to_le_bytes());
3948                bytes.extend_from_slice(&0x5355u16.to_le_bytes());
3949                bytes.extend_from_slice(&0xF2C5u16.to_le_bytes());
3950                bytes.extend_from_slice(&0x5355u16.to_le_bytes());
3951
3952                let hw1: u16 = (0xEA00 | r12) as u16;
3953                let hw2: u16 = ((r12 << 8) | r3) as u16;
3954                bytes.extend_from_slice(&hw1.to_le_bytes());
3955                bytes.extend_from_slice(&hw2.to_le_bytes());
3956
3957                let hw1: u16 = (0xEBA0 | 5) as u16;
3958                let hw2: u16 = ((5 << 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 2
3963                bytes.extend_from_slice(&0xF243u16.to_le_bytes());
3964                bytes.extend_from_slice(&0x3333u16.to_le_bytes());
3965                bytes.extend_from_slice(&0xF2C3u16.to_le_bytes());
3966                bytes.extend_from_slice(&0x3333u16.to_le_bytes());
3967
3968                let hw1: u16 = (0xEA00 | 5) as u16;
3969                let hw2: u16 = ((r12 << 8) | r3) as u16;
3970                bytes.extend_from_slice(&hw1.to_le_bytes());
3971                bytes.extend_from_slice(&hw2.to_le_bytes());
3972
3973                let hw1: u16 = 0xEA4F;
3974                let hw2: u16 = ((5 << 8) | 0x90 | 5) as u16;
3975                bytes.extend_from_slice(&hw1.to_le_bytes());
3976                bytes.extend_from_slice(&hw2.to_le_bytes());
3977
3978                let hw1: u16 = (0xEA00 | 5) as u16;
3979                let hw2: u16 = ((5 << 8) | r3) as u16;
3980                bytes.extend_from_slice(&hw1.to_le_bytes());
3981                bytes.extend_from_slice(&hw2.to_le_bytes());
3982
3983                let hw1: u16 = (0xEB00 | 5) as u16;
3984                let hw2: u16 = ((5 << 8) | r12) as u16;
3985                bytes.extend_from_slice(&hw1.to_le_bytes());
3986                bytes.extend_from_slice(&hw2.to_le_bytes());
3987
3988                // Step 3: LSR.W R12, R5, #4
3989                // imm5=4=00100 → imm3=1, imm2=0, type=01(LSR)
3990                let hw1: u16 = 0xEA4F;
3991                let hw2: u16 = (0x1000 | (r12 << 8) | 0x10 | 5) as u16;
3992                bytes.extend_from_slice(&hw1.to_le_bytes());
3993                bytes.extend_from_slice(&hw2.to_le_bytes());
3994
3995                let hw1: u16 = (0xEB00 | 5) as u16;
3996                let hw2: u16 = ((5 << 8) | r12) as u16;
3997                bytes.extend_from_slice(&hw1.to_le_bytes());
3998                bytes.extend_from_slice(&hw2.to_le_bytes());
3999
4000                // Load 0x0F0F0F0F into R3 (for hi-word)
4001                bytes.extend_from_slice(&0xF640u16.to_le_bytes());
4002                bytes.extend_from_slice(&0x730Fu16.to_le_bytes());
4003                bytes.extend_from_slice(&0xF6C0u16.to_le_bytes());
4004                bytes.extend_from_slice(&0x730Fu16.to_le_bytes());
4005
4006                let hw1: u16 = (0xEA00 | 5) as u16;
4007                let hw2: u16 = ((5 << 8) | r3) as u16;
4008                bytes.extend_from_slice(&hw1.to_le_bytes());
4009                bytes.extend_from_slice(&hw2.to_le_bytes());
4010
4011                // Step 4
4012                bytes.extend_from_slice(&0xF240u16.to_le_bytes());
4013                bytes.extend_from_slice(&0x1301u16.to_le_bytes());
4014                bytes.extend_from_slice(&0xF2C0u16.to_le_bytes());
4015                bytes.extend_from_slice(&0x1301u16.to_le_bytes());
4016
4017                // MUL R5, R5, R3
4018                // MUL T2: hw1 = 0xFB00|Rn, hw2 = 0xF000|(Rd<<8)|Rm
4019                let hw1: u16 = (0xFB00 | 5) as u16;
4020                let hw2: u16 = (0xF000 | (5 << 8) | r3) as u16;
4021                bytes.extend_from_slice(&hw1.to_le_bytes());
4022                bytes.extend_from_slice(&hw2.to_le_bytes());
4023
4024                // LSR.W R5, R5, #24
4025                // imm5=24=11000 → imm3=6, imm2=0, type=01(LSR)
4026                let hw1: u16 = 0xEA4F;
4027                let hw2: u16 = (0x6000 | (5 << 8) | 0x10 | 5) as u16;
4028                bytes.extend_from_slice(&hw1.to_le_bytes());
4029                bytes.extend_from_slice(&hw2.to_le_bytes());
4030
4031                // ADD rd, R4, R5 (combine lo and hi counts)
4032                // ADDS Rd, Rn, Rm (T1): 0001 100 Rm Rn Rd = 0x1800 | (Rm<<6) | (Rn<<3) | Rd
4033                let rd_bits_u16 = rd_bits as u16;
4034                let instr: u16 = 0x1800 | (5 << 6) | (4 << 3) | rd_bits_u16;
4035                bytes.extend_from_slice(&instr.to_le_bytes());
4036
4037                // POP {R3, R4, R5}
4038                bytes.extend_from_slice(&0xBC38u16.to_le_bytes());
4039
4040                // i64.popcnt returns i64, so clear high word: MOV rnhi, #0 (2 bytes)
4041                let mov0: u16 = (0x2000 | (rn_hi_bits << 8)) as u16;
4042                bytes.extend_from_slice(&mov0.to_le_bytes());
4043
4044                Ok(bytes)
4045            }
4046
4047            // I64Extend8S: Sign-extend low 8 bits to 64 bits
4048            // Result: rdlo = sign_extend_8(rnlo), rdhi = rdlo >> 31
4049            ArmOp::I64Extend8S { rdlo, rdhi, rnlo } => {
4050                let rdlo_bits = reg_to_bits(rdlo);
4051                let rdhi_bits = reg_to_bits(rdhi);
4052                let rnlo_bits = reg_to_bits(rnlo);
4053                let mut bytes = Vec::new();
4054
4055                // SXTB.W rdlo, rnlo (sign-extend byte to 32-bit)
4056                // SXTB T2: hw1 = 0xFA4F, hw2 = 0xF0<Rd><Rm>
4057                let hw1: u16 = 0xFA4F_u16;
4058                let hw2: u16 = (0xF080 | (rdlo_bits << 8) | rnlo_bits) as u16;
4059                bytes.extend_from_slice(&hw1.to_le_bytes());
4060                bytes.extend_from_slice(&hw2.to_le_bytes());
4061
4062                // ASR.W rdhi, rdlo, #31 (sign-extend to high word)
4063                // ASR (immediate): hw1 = 0xEA4F, hw2 = imm3:Rd:imm2:type:Rm
4064                // For imm5=31: imm3=111, imm2=11, type=10 (ASR)
4065                // hw2 = (7 << 12) | (rdhi << 8) | (3 << 6) | (2 << 4) | rdlo
4066                let hw1: u16 = 0xEA4F;
4067                let hw2: u16 = (0x70E0 | (rdhi_bits << 8) | rdlo_bits) as u16;
4068                bytes.extend_from_slice(&hw1.to_le_bytes());
4069                bytes.extend_from_slice(&hw2.to_le_bytes());
4070
4071                Ok(bytes)
4072            }
4073
4074            // I64Extend16S: Sign-extend low 16 bits to 64 bits
4075            // Result: rdlo = sign_extend_16(rnlo), rdhi = rdlo >> 31
4076            ArmOp::I64Extend16S { rdlo, rdhi, rnlo } => {
4077                let rdlo_bits = reg_to_bits(rdlo);
4078                let rdhi_bits = reg_to_bits(rdhi);
4079                let rnlo_bits = reg_to_bits(rnlo);
4080                let mut bytes = Vec::new();
4081
4082                // SXTH.W rdlo, rnlo (sign-extend halfword to 32-bit)
4083                // SXTH T2: hw1 = 0xFA0F, hw2 = 0xF0<Rd><Rm>
4084                let hw1: u16 = 0xFA0F_u16;
4085                let hw2: u16 = (0xF080 | (rdlo_bits << 8) | rnlo_bits) as u16;
4086                bytes.extend_from_slice(&hw1.to_le_bytes());
4087                bytes.extend_from_slice(&hw2.to_le_bytes());
4088
4089                // ASR.W rdhi, rdlo, #31 (sign-extend to high word)
4090                let hw1: u16 = 0xEA4F;
4091                let hw2: u16 = (0x70E0 | (rdhi_bits << 8) | rdlo_bits) as u16;
4092                bytes.extend_from_slice(&hw1.to_le_bytes());
4093                bytes.extend_from_slice(&hw2.to_le_bytes());
4094
4095                Ok(bytes)
4096            }
4097
4098            // I64Extend32S: Sign-extend low 32 bits to 64 bits
4099            // Result: rdlo = rnlo, rdhi = rnlo >> 31
4100            ArmOp::I64Extend32S { rdlo, rdhi, rnlo } => {
4101                let rdlo_bits = reg_to_bits(rdlo);
4102                let rdhi_bits = reg_to_bits(rdhi);
4103                let rnlo_bits = reg_to_bits(rnlo);
4104                let mut bytes = Vec::new();
4105
4106                // MOV rdlo, rnlo (if different)
4107                if rdlo_bits != rnlo_bits {
4108                    // MOV Rd, Rm (16-bit): 0100 0110 D Rm Rd[2:0]
4109                    let d_bit = ((rdlo_bits >> 3) & 1) as u16;
4110                    let mov: u16 = 0x4600
4111                        | (d_bit << 7)
4112                        | ((rnlo_bits as u16) << 3)
4113                        | ((rdlo_bits & 0x7) as u16);
4114                    bytes.extend_from_slice(&mov.to_le_bytes());
4115                }
4116
4117                // ASR.W rdhi, rnlo, #31 (sign-extend to high word)
4118                let hw1: u16 = 0xEA4F;
4119                let hw2: u16 = (0x70E0 | (rdhi_bits << 8) | rnlo_bits) as u16;
4120                bytes.extend_from_slice(&hw1.to_le_bytes());
4121                bytes.extend_from_slice(&hw2.to_le_bytes());
4122
4123                Ok(bytes)
4124            }
4125
4126            // SelectMove: IT <cond>; MOV{cond} rd, rm
4127            // Conditional move: only execute MOV if condition is true
4128            ArmOp::SelectMove { rd, rm, cond } => {
4129                let rd_bits = reg_to_bits(rd) as u16;
4130                let rm_bits = reg_to_bits(rm) as u16;
4131
4132                // Condition code encoding for IT block
4133                use synth_synthesis::Condition;
4134                let cond_bits: u16 = match cond {
4135                    Condition::EQ => 0x0, // Equal
4136                    Condition::NE => 0x1, // Not equal
4137                    Condition::HS => 0x2, // Higher or same (unsigned >=)
4138                    Condition::LO => 0x3, // Lower (unsigned <)
4139                    Condition::HI => 0x8, // Higher (unsigned >)
4140                    Condition::LS => 0x9, // Lower or same (unsigned <=)
4141                    Condition::GE => 0xA, // Greater or equal (signed)
4142                    Condition::LT => 0xB, // Less than (signed)
4143                    Condition::GT => 0xC, // Greater than (signed)
4144                    Condition::LE => 0xD, // Less or equal (signed)
4145                };
4146
4147                // IT <cond>: single Then block (mask = 0x8 for T only)
4148                // IT instruction: 1011 1111 firstcond mask
4149                let it_instr: u16 = 0xBF00 | (cond_bits << 4) | 0x8;
4150
4151                // MOV Rd, Rm (16-bit): 0100 0110 D Rm Rd[2:0]
4152                // This MOV will only execute if condition is true due to IT block
4153                let d_bit = (rd_bits >> 3) & 1;
4154                let mov_instr: u16 = 0x4600 | (d_bit << 7) | (rm_bits << 3) | (rd_bits & 0x7);
4155
4156                // Emit: IT <cond>, MOV rd, rm
4157                let mut bytes = it_instr.to_le_bytes().to_vec();
4158                bytes.extend_from_slice(&mov_instr.to_le_bytes());
4159                Ok(bytes)
4160            }
4161
4162            // Popcnt: Population count (count set bits)
4163            // ARM Cortex-M has no native POPCNT, so we implement the bit manipulation algorithm:
4164            // x = x - ((x >> 1) & 0x55555555);
4165            // x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
4166            // x = (x + (x >> 4)) & 0x0F0F0F0F;
4167            // x = x + (x >> 8);
4168            // x = x + (x >> 16);
4169            // return x & 0x3F;
4170            //
4171            // Uses rd as working register and R12 as scratch for constants
4172            ArmOp::Popcnt { rd, rm } => {
4173                let mut bytes = Vec::new();
4174
4175                // First, move rm to rd if they're different
4176                if rd != rm {
4177                    let rd_bits = reg_to_bits(rd) as u16;
4178                    let rm_bits = reg_to_bits(rm) as u16;
4179                    // MOV Rd, Rm (16-bit): 0100 0110 D Rm Rd[2:0]
4180                    let d_bit = (rd_bits >> 3) & 1;
4181                    let mov_instr: u16 = 0x4600 | (d_bit << 7) | (rm_bits << 3) | (rd_bits & 0x7);
4182                    bytes.extend_from_slice(&mov_instr.to_le_bytes());
4183                }
4184
4185                // Step 1: x = x - ((x >> 1) & 0x55555555)
4186                // Load 0x55555555 into R12
4187                bytes.extend_from_slice(&self.encode_thumb32_movw_raw(12, 0x5555)?);
4188                bytes.extend_from_slice(&self.encode_thumb32_movt_raw(12, 0x5555)?);
4189
4190                // R12_temp = rd >> 1
4191                // We need a second scratch register. Use R11.
4192                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(11, reg_to_bits(rd), 1)?);
4193
4194                // R11 = R11 & R12 (R11 = (x >> 1) & 0x55555555)
4195                bytes.extend_from_slice(&self.encode_thumb32_and_reg_raw(11, 11, 12)?);
4196
4197                // rd = rd - R11
4198                bytes.extend_from_slice(&self.encode_thumb32_sub_reg_raw(
4199                    reg_to_bits(rd),
4200                    reg_to_bits(rd),
4201                    11,
4202                )?);
4203
4204                // Step 2: x = (x & 0x33333333) + ((x >> 2) & 0x33333333)
4205                // Load 0x33333333 into R12
4206                bytes.extend_from_slice(&self.encode_thumb32_movw_raw(12, 0x3333)?);
4207                bytes.extend_from_slice(&self.encode_thumb32_movt_raw(12, 0x3333)?);
4208
4209                // R11 = rd & R12
4210                bytes.extend_from_slice(&self.encode_thumb32_and_reg_raw(
4211                    11,
4212                    reg_to_bits(rd),
4213                    12,
4214                )?);
4215
4216                // rd = rd >> 2
4217                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(
4218                    reg_to_bits(rd),
4219                    reg_to_bits(rd),
4220                    2,
4221                )?);
4222
4223                // rd = rd & R12
4224                bytes.extend_from_slice(&self.encode_thumb32_and_reg_raw(
4225                    reg_to_bits(rd),
4226                    reg_to_bits(rd),
4227                    12,
4228                )?);
4229
4230                // rd = rd + R11
4231                bytes.extend_from_slice(&self.encode_thumb32_add_reg_raw(
4232                    reg_to_bits(rd),
4233                    reg_to_bits(rd),
4234                    11,
4235                )?);
4236
4237                // Step 3: x = (x + (x >> 4)) & 0x0F0F0F0F
4238                // R11 = rd >> 4
4239                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(11, reg_to_bits(rd), 4)?);
4240
4241                // rd = rd + R11
4242                bytes.extend_from_slice(&self.encode_thumb32_add_reg_raw(
4243                    reg_to_bits(rd),
4244                    reg_to_bits(rd),
4245                    11,
4246                )?);
4247
4248                // Load 0x0F0F0F0F into R12
4249                bytes.extend_from_slice(&self.encode_thumb32_movw_raw(12, 0x0F0F)?);
4250                bytes.extend_from_slice(&self.encode_thumb32_movt_raw(12, 0x0F0F)?);
4251
4252                // rd = rd & R12
4253                bytes.extend_from_slice(&self.encode_thumb32_and_reg_raw(
4254                    reg_to_bits(rd),
4255                    reg_to_bits(rd),
4256                    12,
4257                )?);
4258
4259                // Step 4: x = x + (x >> 8)
4260                // R11 = rd >> 8
4261                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(11, reg_to_bits(rd), 8)?);
4262
4263                // rd = rd + R11
4264                bytes.extend_from_slice(&self.encode_thumb32_add_reg_raw(
4265                    reg_to_bits(rd),
4266                    reg_to_bits(rd),
4267                    11,
4268                )?);
4269
4270                // Step 5: x = x + (x >> 16)
4271                // R11 = rd >> 16
4272                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(11, reg_to_bits(rd), 16)?);
4273
4274                // rd = rd + R11
4275                bytes.extend_from_slice(&self.encode_thumb32_add_reg_raw(
4276                    reg_to_bits(rd),
4277                    reg_to_bits(rd),
4278                    11,
4279                )?);
4280
4281                // Step 6: return x & 0x3F
4282                // AND with 0x3F (small immediate, can use BIC or AND with immediate)
4283                bytes.extend_from_slice(&self.encode_thumb32_and_imm_raw(
4284                    reg_to_bits(rd),
4285                    reg_to_bits(rd),
4286                    0x3F,
4287                )?);
4288
4289                Ok(bytes)
4290            }
4291
4292            // I64DivU: 64-bit unsigned division using binary long division
4293            // Input: R0:R1 = dividend, R2:R3 = divisor
4294            // Output: R0:R1 = quotient
4295            // Uses: R4-R7, R12 as loop counter (avoid R8 for Renode compatibility)
4296            ArmOp::I64DivU {
4297                rdlo: _,
4298                rdhi: _,
4299                rnlo: _,
4300                rnhi: _,
4301                rmlo: _,
4302                rmhi: _,
4303            } => {
4304                let mut bytes = Vec::new();
4305
4306                // PUSH {R4-R7} - save scratch registers (NO LR — this is inline code)
4307                // 16-bit PUSH: 1011 010 M rrrrrrrr where M=0 (no LR), r=R4-R7 = 0xF0
4308                // Encoding: 1011 0100 1111 0000 = 0xB4F0
4309                bytes.extend_from_slice(&0xB4F0u16.to_le_bytes());
4310
4311                // Initialize quotient (R4:R5) = 0
4312                bytes.extend_from_slice(&0x2400u16.to_le_bytes()); // MOV R4, #0
4313                bytes.extend_from_slice(&0x2500u16.to_le_bytes()); // MOV R5, #0
4314
4315                // Initialize remainder (R6:R7) = 0
4316                bytes.extend_from_slice(&0x2600u16.to_le_bytes()); // MOV R6, #0
4317                bytes.extend_from_slice(&0x2700u16.to_le_bytes()); // MOV R7, #0
4318
4319                // Initialize loop counter R12 = 64 (use R12 scratch instead of R8)
4320                // MOV.W R12, #64: F04F 0C40
4321                bytes.extend_from_slice(&0xF04Fu16.to_le_bytes());
4322                bytes.extend_from_slice(&0x0C40u16.to_le_bytes());
4323
4324                // Loop start
4325                let loop_start = bytes.len();
4326
4327                // === Loop body: process one bit ===
4328
4329                // 1. Shift quotient R4:R5 left by 1
4330                // LSLS R5, R5, #1 (16-bit: 0000 0010 1010 1101 = 0x006D -> actually 0x002D for LSL R5,R5,#1)
4331                // LSL Rd, Rm, #imm5: 000 00 imm5 Rm Rd = 000 00 00001 101 101 = 0x006D
4332                bytes.extend_from_slice(&0x006Du16.to_le_bytes()); // LSLS R5, R5, #1
4333                // Get carry from R4 into R5: ORR R5, R5, R4 LSR #31
4334                // Thumb-2 ORR with shifted register: EA45 75D4 = ORR.W R5, R5, R4, LSR #31
4335                // 11101010 010 S Rn | 0 imm3 Rd imm2 type Rm
4336                // type=01 (LSR), imm5=31 (imm3=111, imm2=11)
4337                bytes.extend_from_slice(&0xEA45u16.to_le_bytes());
4338                bytes.extend_from_slice(&0x75D4u16.to_le_bytes()); // ORR.W R5, R5, R4, LSR #31
4339                // LSLS R4, R4, #1: 000 00 00001 100 100 = 0x0064
4340                bytes.extend_from_slice(&0x0064u16.to_le_bytes()); // LSLS R4, R4, #1
4341
4342                // 2. Shift remainder R6:R7 left by 1, OR in MSB of dividend R1
4343                // LSLS R7, R7, #1
4344                bytes.extend_from_slice(&0x007Fu16.to_le_bytes()); // LSLS R7, R7, #1
4345                // ORR.W R7, R7, R6, LSR #31
4346                bytes.extend_from_slice(&0xEA47u16.to_le_bytes());
4347                bytes.extend_from_slice(&0x77D6u16.to_le_bytes());
4348                // LSLS R6, R6, #1
4349                bytes.extend_from_slice(&0x0076u16.to_le_bytes()); // LSLS R6, R6, #1
4350                // ORR.W R6, R6, R1, LSR #31 (bring in MSB of dividend high)
4351                bytes.extend_from_slice(&0xEA46u16.to_le_bytes());
4352                bytes.extend_from_slice(&0x76D1u16.to_le_bytes());
4353
4354                // 3. Shift dividend R0:R1 left by 1
4355                // LSLS R1, R1, #1
4356                bytes.extend_from_slice(&0x0049u16.to_le_bytes()); // LSLS R1, R1, #1
4357                // ORR.W R1, R1, R0, LSR #31
4358                bytes.extend_from_slice(&0xEA41u16.to_le_bytes());
4359                bytes.extend_from_slice(&0x71D0u16.to_le_bytes());
4360                // LSLS R0, R0, #1
4361                bytes.extend_from_slice(&0x0040u16.to_le_bytes()); // LSLS R0, R0, #1
4362
4363                // 4. Compare remainder >= divisor (64-bit unsigned comparison)
4364                // Compare high words first: CMP R7, R3
4365                // CMP Rn, Rm encoding: 0x4280 | (Rm << 3) | Rn
4366                bytes.extend_from_slice(&0x429Fu16.to_le_bytes()); // CMP R7, R3 (16-bit)
4367                // BHI means R7 > R3 (unsigned) - definitely subtract
4368                // BLO means R7 < R3 - definitely don't subtract
4369                // BEQ means need to check low words
4370
4371                // If high > divisor high: branch to subtract (forward +offset)
4372                // BHI.N +6 (skip CMP, skip BLO, do subtract)
4373                // BHI: 1101 1000 offset8 where cond=1000 (HI)
4374                bytes.extend_from_slice(&0xD802u16.to_le_bytes()); // BHI +4 (to subtract block)
4375
4376                // If high < divisor high: branch past subtract
4377                // BLO.N +10 (skip to decrement)
4378                bytes.extend_from_slice(&0xD306u16.to_le_bytes()); // BLO/BCC +12 (past subtract)
4379
4380                // High words equal, compare low: CMP R6, R2
4381                bytes.extend_from_slice(&0x4296u16.to_le_bytes()); // CMP R6, R2 (16-bit)
4382                // BLO/BCC past subtract (skip SUBS+SBC.W+ORR.W = 10 bytes = 4 halfwords from PC+4)
4383                bytes.extend_from_slice(&0xD304u16.to_le_bytes()); // BCC +4 halfwords (past subtract)
4384
4385                // === Subtract block: remainder -= divisor, quotient |= 1 ===
4386                // SUBS R6, R6, R2
4387                bytes.extend_from_slice(&0x1AB6u16.to_le_bytes()); // SUBS R6, R6, R2 (16-bit)
4388                // SBC R7, R7, R3 (with borrow)
4389                // Thumb-2 SBC.W: EB67 0703 = SBC.W R7, R7, R3
4390                bytes.extend_from_slice(&0xEB67u16.to_le_bytes());
4391                bytes.extend_from_slice(&0x0703u16.to_le_bytes());
4392                // ORR R4, R4, #1 (set bit 0 of quotient low)
4393                bytes.extend_from_slice(&0xF044u16.to_le_bytes()); // ORR.W R4, R4, #1
4394                bytes.extend_from_slice(&0x0401u16.to_le_bytes());
4395
4396                // === Decrement counter and loop ===
4397                // SUBS.W R12, R12, #1 (decrement loop counter)
4398                // SUBS.W R12, R12, #1: F1BC 0C01
4399                bytes.extend_from_slice(&0xF1BCu16.to_le_bytes());
4400                bytes.extend_from_slice(&0x0C01u16.to_le_bytes());
4401
4402                // BNE back to loop_start
4403                let branch_offset_bytes = bytes.len() - loop_start + 4; // +4 for pipeline
4404                let offset_halfwords = -((branch_offset_bytes / 2) as i16);
4405                let bne_encoding = 0xD100u16 | ((offset_halfwords as u16) & 0xFF);
4406                bytes.extend_from_slice(&bne_encoding.to_le_bytes());
4407
4408                // === Loop done, move quotient to R0:R1 ===
4409                bytes.extend_from_slice(&0x4620u16.to_le_bytes()); // MOV R0, R4
4410                bytes.extend_from_slice(&0x4629u16.to_le_bytes()); // MOV R1, R5
4411
4412                // POP {R4-R7} - restore scratch registers (NO PC — inline code continues)
4413                // 16-bit POP: 1011 110 P rrrrrrrr where P=0 (no PC), r=R4-R7 = 0xF0
4414                // Encoding: 1011 1100 1111 0000 = 0xBCF0
4415                bytes.extend_from_slice(&0xBCF0u16.to_le_bytes());
4416
4417                Ok(bytes)
4418            }
4419
4420            // I64DivS: 64-bit signed division
4421            // Converts to unsigned, divides, then applies sign
4422            // Input: R0:R1 = dividend (signed), R2:R3 = divisor (signed)
4423            // Output: R0:R1 = quotient (signed)
4424            ArmOp::I64DivS {
4425                rdlo: _,
4426                rdhi: _,
4427                rnlo: _,
4428                rnhi: _,
4429                rmlo: _,
4430                rmhi: _,
4431            } => {
4432                let mut bytes = Vec::new();
4433
4434                // PUSH {R4-R11} - save scratch registers (NO LR — inline code)
4435                bytes.extend_from_slice(&0xE92Du16.to_le_bytes());
4436                bytes.extend_from_slice(&0x0FF0u16.to_le_bytes());
4437
4438                // Save result sign in R9: R9 = R1 XOR R3 (sign bit = MSB)
4439                // EOR.W R9, R1, R3
4440                bytes.extend_from_slice(&0xEA81u16.to_le_bytes());
4441                bytes.extend_from_slice(&0x0903u16.to_le_bytes());
4442
4443                // If dividend negative (R1 MSB set), negate it
4444                // TST R1, R1 (check sign)
4445                bytes.extend_from_slice(&0x4209u16.to_le_bytes()); // TST R1, R1
4446                // BPL skip_neg_dividend (+10 bytes = 5 halfwords)
4447                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4448
4449                // Negate R0:R1 (64-bit): RSBS R0, R0, #0; SBC R1, R1, R1 LSL #1
4450                // Actually: MVN R0, R0; MVN R1, R1; ADDS R0, R0, #1; ADC R1, R1, #0
4451                bytes.extend_from_slice(&0x43C0u16.to_le_bytes()); // MVNS R0, R0
4452                bytes.extend_from_slice(&0x43C9u16.to_le_bytes()); // MVNS R1, R1
4453                bytes.extend_from_slice(&0x1C40u16.to_le_bytes()); // ADDS R0, R0, #1
4454                bytes.extend_from_slice(&0xF141u16.to_le_bytes()); // ADC.W R1, R1, #0
4455                bytes.extend_from_slice(&0x0100u16.to_le_bytes());
4456
4457                // If divisor negative (R3 MSB set), negate it
4458                bytes.extend_from_slice(&0x421Bu16.to_le_bytes()); // TST R3, R3
4459                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4460
4461                // Negate R2:R3
4462                bytes.extend_from_slice(&0x43D2u16.to_le_bytes()); // MVNS R2, R2
4463                bytes.extend_from_slice(&0x43DBu16.to_le_bytes()); // MVNS R3, R3
4464                bytes.extend_from_slice(&0x1C52u16.to_le_bytes()); // ADDS R2, R2, #1
4465                bytes.extend_from_slice(&0xF143u16.to_le_bytes()); // ADC.W R3, R3, #0
4466                bytes.extend_from_slice(&0x0300u16.to_le_bytes());
4467
4468                // === Now do unsigned division (same as I64DivU) ===
4469                // Initialize quotient (R4:R5) = 0
4470                bytes.extend_from_slice(&0x2400u16.to_le_bytes());
4471                bytes.extend_from_slice(&0x2500u16.to_le_bytes());
4472                // Initialize remainder (R6:R7) = 0
4473                bytes.extend_from_slice(&0x2600u16.to_le_bytes());
4474                bytes.extend_from_slice(&0x2700u16.to_le_bytes());
4475                // Initialize loop counter R8 = 64
4476                bytes.extend_from_slice(&0xF04Fu16.to_le_bytes());
4477                bytes.extend_from_slice(&0x0840u16.to_le_bytes());
4478
4479                let loop_start = bytes.len();
4480
4481                // Shift quotient left
4482                bytes.extend_from_slice(&0x006Du16.to_le_bytes()); // LSLS R5, R5, #1
4483                bytes.extend_from_slice(&0xEA45u16.to_le_bytes()); // ORR.W R5, R5, R4, LSR #31
4484                bytes.extend_from_slice(&0x75D4u16.to_le_bytes());
4485                bytes.extend_from_slice(&0x0064u16.to_le_bytes()); // LSLS R4, R4, #1
4486
4487                // Shift remainder left, OR in MSB of dividend
4488                bytes.extend_from_slice(&0x007Fu16.to_le_bytes()); // LSLS R7, R7, #1
4489                bytes.extend_from_slice(&0xEA47u16.to_le_bytes()); // ORR.W R7, R7, R6, LSR #31
4490                bytes.extend_from_slice(&0x77D6u16.to_le_bytes());
4491                bytes.extend_from_slice(&0x0076u16.to_le_bytes()); // LSLS R6, R6, #1
4492                bytes.extend_from_slice(&0xEA46u16.to_le_bytes()); // ORR.W R6, R6, R1, LSR #31
4493                bytes.extend_from_slice(&0x76D1u16.to_le_bytes());
4494
4495                // Shift dividend left
4496                bytes.extend_from_slice(&0x0049u16.to_le_bytes()); // LSLS R1, R1, #1
4497                bytes.extend_from_slice(&0xEA41u16.to_le_bytes()); // ORR.W R1, R1, R0, LSR #31
4498                bytes.extend_from_slice(&0x71D0u16.to_le_bytes());
4499                bytes.extend_from_slice(&0x0040u16.to_le_bytes()); // LSLS R0, R0, #1
4500
4501                // Compare and conditionally subtract
4502                bytes.extend_from_slice(&0x429Fu16.to_le_bytes()); // CMP R7, R3
4503                bytes.extend_from_slice(&0xD802u16.to_le_bytes()); // BHI +4
4504                bytes.extend_from_slice(&0xD306u16.to_le_bytes()); // BCC +12
4505                bytes.extend_from_slice(&0x4296u16.to_le_bytes()); // CMP R6, R2
4506                bytes.extend_from_slice(&0xD304u16.to_le_bytes()); // BCC +4 halfwords
4507
4508                // Subtract and set quotient bit
4509                bytes.extend_from_slice(&0x1AB6u16.to_le_bytes()); // SUBS R6, R6, R2
4510                bytes.extend_from_slice(&0xEB67u16.to_le_bytes()); // SBC.W R7, R7, R3
4511                bytes.extend_from_slice(&0x0703u16.to_le_bytes());
4512                bytes.extend_from_slice(&0xF044u16.to_le_bytes()); // ORR.W R4, R4, #1
4513                bytes.extend_from_slice(&0x0401u16.to_le_bytes());
4514
4515                // Decrement and loop
4516                bytes.extend_from_slice(&0xF1B8u16.to_le_bytes()); // SUB.W R8, R8, #1
4517                bytes.extend_from_slice(&0x0801u16.to_le_bytes());
4518
4519                let branch_offset_bytes = bytes.len() - loop_start + 4;
4520                let offset_halfwords = -((branch_offset_bytes / 2) as i16);
4521                let bne_encoding = 0xD100u16 | ((offset_halfwords as u16) & 0xFF);
4522                bytes.extend_from_slice(&bne_encoding.to_le_bytes());
4523
4524                // Move quotient to R0:R1
4525                bytes.extend_from_slice(&0x4620u16.to_le_bytes()); // MOV R0, R4
4526                bytes.extend_from_slice(&0x4629u16.to_le_bytes()); // MOV R1, R5
4527
4528                // If result should be negative (R9 MSB set), negate R0:R1
4529                bytes.extend_from_slice(&0xF1B9u16.to_le_bytes()); // TST.W R9, R9 (check MSB)
4530                bytes.extend_from_slice(&0x0F00u16.to_le_bytes());
4531                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8 (skip negation)
4532
4533                // Negate result R0:R1
4534                bytes.extend_from_slice(&0x43C0u16.to_le_bytes()); // MVNS R0, R0
4535                bytes.extend_from_slice(&0x43C9u16.to_le_bytes()); // MVNS R1, R1
4536                bytes.extend_from_slice(&0x1C40u16.to_le_bytes()); // ADDS R0, R0, #1
4537                bytes.extend_from_slice(&0xF141u16.to_le_bytes()); // ADC.W R1, R1, #0
4538                bytes.extend_from_slice(&0x0100u16.to_le_bytes());
4539
4540                // POP {R4-R11} - restore scratch registers (NO PC — inline code continues)
4541                bytes.extend_from_slice(&0xE8BDu16.to_le_bytes());
4542                bytes.extend_from_slice(&0x0FF0u16.to_le_bytes());
4543
4544                Ok(bytes)
4545            }
4546
4547            // I64RemU: 64-bit unsigned remainder using binary long division
4548            // Same algorithm as I64DivU but returns remainder instead of quotient
4549            // Input: R0:R1 = dividend, R2:R3 = divisor
4550            // Output: R0:R1 = remainder
4551            ArmOp::I64RemU {
4552                rdlo: _,
4553                rdhi: _,
4554                rnlo: _,
4555                rnhi: _,
4556                rmlo: _,
4557                rmhi: _,
4558            } => {
4559                let mut bytes = Vec::new();
4560
4561                // PUSH {R4-R8} - save scratch registers (NO LR — inline code)
4562                bytes.extend_from_slice(&0xE92Du16.to_le_bytes());
4563                bytes.extend_from_slice(&0x01F0u16.to_le_bytes());
4564
4565                // Initialize quotient (R4:R5) = 0 (computed but not returned)
4566                bytes.extend_from_slice(&0x2400u16.to_le_bytes());
4567                bytes.extend_from_slice(&0x2500u16.to_le_bytes());
4568                // Initialize remainder (R6:R7) = 0
4569                bytes.extend_from_slice(&0x2600u16.to_le_bytes());
4570                bytes.extend_from_slice(&0x2700u16.to_le_bytes());
4571                // Initialize loop counter R8 = 64
4572                bytes.extend_from_slice(&0xF04Fu16.to_le_bytes());
4573                bytes.extend_from_slice(&0x0840u16.to_le_bytes());
4574
4575                let loop_start = bytes.len();
4576
4577                // Shift quotient left (not needed for result, but keeps algorithm same)
4578                bytes.extend_from_slice(&0x006Du16.to_le_bytes()); // LSLS R5, R5, #1
4579                bytes.extend_from_slice(&0xEA45u16.to_le_bytes()); // ORR.W R5, R5, R4, LSR #31
4580                bytes.extend_from_slice(&0x75D4u16.to_le_bytes());
4581                bytes.extend_from_slice(&0x0064u16.to_le_bytes()); // LSLS R4, R4, #1
4582
4583                // Shift remainder left, OR in MSB of dividend
4584                bytes.extend_from_slice(&0x007Fu16.to_le_bytes()); // LSLS R7, R7, #1
4585                bytes.extend_from_slice(&0xEA47u16.to_le_bytes()); // ORR.W R7, R7, R6, LSR #31
4586                bytes.extend_from_slice(&0x77D6u16.to_le_bytes());
4587                bytes.extend_from_slice(&0x0076u16.to_le_bytes()); // LSLS R6, R6, #1
4588                bytes.extend_from_slice(&0xEA46u16.to_le_bytes()); // ORR.W R6, R6, R1, LSR #31
4589                bytes.extend_from_slice(&0x76D1u16.to_le_bytes());
4590
4591                // Shift dividend left
4592                bytes.extend_from_slice(&0x0049u16.to_le_bytes()); // LSLS R1, R1, #1
4593                bytes.extend_from_slice(&0xEA41u16.to_le_bytes()); // ORR.W R1, R1, R0, LSR #31
4594                bytes.extend_from_slice(&0x71D0u16.to_le_bytes());
4595                bytes.extend_from_slice(&0x0040u16.to_le_bytes()); // LSLS R0, R0, #1
4596
4597                // Compare and conditionally subtract
4598                bytes.extend_from_slice(&0x429Fu16.to_le_bytes()); // CMP R7, R3
4599                bytes.extend_from_slice(&0xD802u16.to_le_bytes()); // BHI +4
4600                bytes.extend_from_slice(&0xD306u16.to_le_bytes()); // BCC +12
4601                bytes.extend_from_slice(&0x4296u16.to_le_bytes()); // CMP R6, R2
4602                bytes.extend_from_slice(&0xD304u16.to_le_bytes()); // BCC +4 halfwords
4603
4604                // Subtract and set quotient bit
4605                bytes.extend_from_slice(&0x1AB6u16.to_le_bytes()); // SUBS R6, R6, R2
4606                bytes.extend_from_slice(&0xEB67u16.to_le_bytes()); // SBC.W R7, R7, R3
4607                bytes.extend_from_slice(&0x0703u16.to_le_bytes());
4608                bytes.extend_from_slice(&0xF044u16.to_le_bytes()); // ORR.W R4, R4, #1
4609                bytes.extend_from_slice(&0x0401u16.to_le_bytes());
4610
4611                // Decrement and loop
4612                bytes.extend_from_slice(&0xF1B8u16.to_le_bytes()); // SUB.W R8, R8, #1
4613                bytes.extend_from_slice(&0x0801u16.to_le_bytes());
4614
4615                let branch_offset_bytes = bytes.len() - loop_start + 4;
4616                let offset_halfwords = -((branch_offset_bytes / 2) as i16);
4617                let bne_encoding = 0xD100u16 | ((offset_halfwords as u16) & 0xFF);
4618                bytes.extend_from_slice(&bne_encoding.to_le_bytes());
4619
4620                // Move REMAINDER to R0:R1 (difference from I64DivU)
4621                bytes.extend_from_slice(&0x4630u16.to_le_bytes()); // MOV R0, R6
4622                bytes.extend_from_slice(&0x4639u16.to_le_bytes()); // MOV R1, R7
4623
4624                // POP {R4-R8} - restore scratch registers (NO PC — inline code continues)
4625                bytes.extend_from_slice(&0xE8BDu16.to_le_bytes());
4626                bytes.extend_from_slice(&0x01F0u16.to_le_bytes());
4627
4628                Ok(bytes)
4629            }
4630
4631            // I64RemS: 64-bit signed remainder
4632            // Remainder sign follows dividend sign (not quotient rule)
4633            // Input: R0:R1 = dividend (signed), R2:R3 = divisor (signed)
4634            // Output: R0:R1 = remainder (signed, same sign as dividend)
4635            ArmOp::I64RemS {
4636                rdlo: _,
4637                rdhi: _,
4638                rnlo: _,
4639                rnhi: _,
4640                rmlo: _,
4641                rmhi: _,
4642            } => {
4643                let mut bytes = Vec::new();
4644
4645                // PUSH {R4-R11} - save scratch registers (NO LR — inline code)
4646                bytes.extend_from_slice(&0xE92Du16.to_le_bytes());
4647                bytes.extend_from_slice(&0x0FF0u16.to_le_bytes());
4648
4649                // Save dividend sign in R9 (remainder sign = dividend sign)
4650                // MOV R9, R1 (just need the sign bit)
4651                bytes.extend_from_slice(&0x4689u16.to_le_bytes()); // MOV R9, R1
4652
4653                // If dividend negative (R1 MSB set), negate it
4654                bytes.extend_from_slice(&0x4209u16.to_le_bytes()); // TST R1, R1
4655                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4656
4657                // Negate R0:R1
4658                bytes.extend_from_slice(&0x43C0u16.to_le_bytes()); // MVNS R0, R0
4659                bytes.extend_from_slice(&0x43C9u16.to_le_bytes()); // MVNS R1, R1
4660                bytes.extend_from_slice(&0x1C40u16.to_le_bytes()); // ADDS R0, R0, #1
4661                bytes.extend_from_slice(&0xF141u16.to_le_bytes()); // ADC.W R1, R1, #0
4662                bytes.extend_from_slice(&0x0100u16.to_le_bytes());
4663
4664                // If divisor negative (R3 MSB set), negate it
4665                bytes.extend_from_slice(&0x421Bu16.to_le_bytes()); // TST R3, R3
4666                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4667
4668                // Negate R2:R3
4669                bytes.extend_from_slice(&0x43D2u16.to_le_bytes()); // MVNS R2, R2
4670                bytes.extend_from_slice(&0x43DBu16.to_le_bytes()); // MVNS R3, R3
4671                bytes.extend_from_slice(&0x1C52u16.to_le_bytes()); // ADDS R2, R2, #1
4672                bytes.extend_from_slice(&0xF143u16.to_le_bytes()); // ADC.W R3, R3, #0
4673                bytes.extend_from_slice(&0x0300u16.to_le_bytes());
4674
4675                // === Unsigned division algorithm ===
4676                // Initialize quotient (R4:R5) = 0
4677                bytes.extend_from_slice(&0x2400u16.to_le_bytes());
4678                bytes.extend_from_slice(&0x2500u16.to_le_bytes());
4679                // Initialize remainder (R6:R7) = 0
4680                bytes.extend_from_slice(&0x2600u16.to_le_bytes());
4681                bytes.extend_from_slice(&0x2700u16.to_le_bytes());
4682                // Initialize loop counter R8 = 64
4683                bytes.extend_from_slice(&0xF04Fu16.to_le_bytes());
4684                bytes.extend_from_slice(&0x0840u16.to_le_bytes());
4685
4686                let loop_start = bytes.len();
4687
4688                // Shift quotient left
4689                bytes.extend_from_slice(&0x006Du16.to_le_bytes()); // LSLS R5, R5, #1
4690                bytes.extend_from_slice(&0xEA45u16.to_le_bytes()); // ORR.W R5, R5, R4, LSR #31
4691                bytes.extend_from_slice(&0x75D4u16.to_le_bytes());
4692                bytes.extend_from_slice(&0x0064u16.to_le_bytes()); // LSLS R4, R4, #1
4693
4694                // Shift remainder left, OR in MSB of dividend
4695                bytes.extend_from_slice(&0x007Fu16.to_le_bytes()); // LSLS R7, R7, #1
4696                bytes.extend_from_slice(&0xEA47u16.to_le_bytes()); // ORR.W R7, R7, R6, LSR #31
4697                bytes.extend_from_slice(&0x77D6u16.to_le_bytes());
4698                bytes.extend_from_slice(&0x0076u16.to_le_bytes()); // LSLS R6, R6, #1
4699                bytes.extend_from_slice(&0xEA46u16.to_le_bytes()); // ORR.W R6, R6, R1, LSR #31
4700                bytes.extend_from_slice(&0x76D1u16.to_le_bytes());
4701
4702                // Shift dividend left
4703                bytes.extend_from_slice(&0x0049u16.to_le_bytes()); // LSLS R1, R1, #1
4704                bytes.extend_from_slice(&0xEA41u16.to_le_bytes()); // ORR.W R1, R1, R0, LSR #31
4705                bytes.extend_from_slice(&0x71D0u16.to_le_bytes());
4706                bytes.extend_from_slice(&0x0040u16.to_le_bytes()); // LSLS R0, R0, #1
4707
4708                // Compare and conditionally subtract
4709                bytes.extend_from_slice(&0x429Fu16.to_le_bytes()); // CMP R7, R3
4710                bytes.extend_from_slice(&0xD802u16.to_le_bytes()); // BHI +4
4711                bytes.extend_from_slice(&0xD306u16.to_le_bytes()); // BCC +12
4712                bytes.extend_from_slice(&0x4296u16.to_le_bytes()); // CMP R6, R2
4713                bytes.extend_from_slice(&0xD304u16.to_le_bytes()); // BCC +4 halfwords
4714
4715                // Subtract and set quotient bit
4716                bytes.extend_from_slice(&0x1AB6u16.to_le_bytes()); // SUBS R6, R6, R2
4717                bytes.extend_from_slice(&0xEB67u16.to_le_bytes()); // SBC.W R7, R7, R3
4718                bytes.extend_from_slice(&0x0703u16.to_le_bytes());
4719                bytes.extend_from_slice(&0xF044u16.to_le_bytes()); // ORR.W R4, R4, #1
4720                bytes.extend_from_slice(&0x0401u16.to_le_bytes());
4721
4722                // Decrement and loop
4723                bytes.extend_from_slice(&0xF1B8u16.to_le_bytes()); // SUB.W R8, R8, #1
4724                bytes.extend_from_slice(&0x0801u16.to_le_bytes());
4725
4726                let branch_offset_bytes = bytes.len() - loop_start + 4;
4727                let offset_halfwords = -((branch_offset_bytes / 2) as i16);
4728                let bne_encoding = 0xD100u16 | ((offset_halfwords as u16) & 0xFF);
4729                bytes.extend_from_slice(&bne_encoding.to_le_bytes());
4730
4731                // Move remainder to R0:R1
4732                bytes.extend_from_slice(&0x4630u16.to_le_bytes()); // MOV R0, R6
4733                bytes.extend_from_slice(&0x4639u16.to_le_bytes()); // MOV R1, R7
4734
4735                // If original dividend was negative (R9 MSB set), negate remainder
4736                bytes.extend_from_slice(&0xF1B9u16.to_le_bytes()); // TST.W R9, R9
4737                bytes.extend_from_slice(&0x0F00u16.to_le_bytes());
4738                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4739
4740                // Negate result R0:R1
4741                bytes.extend_from_slice(&0x43C0u16.to_le_bytes()); // MVNS R0, R0
4742                bytes.extend_from_slice(&0x43C9u16.to_le_bytes()); // MVNS R1, R1
4743                bytes.extend_from_slice(&0x1C40u16.to_le_bytes()); // ADDS R0, R0, #1
4744                bytes.extend_from_slice(&0xF141u16.to_le_bytes()); // ADC.W R1, R1, #0
4745                bytes.extend_from_slice(&0x0100u16.to_le_bytes());
4746
4747                // POP {R4-R11} - restore scratch registers (NO PC — inline code continues)
4748                bytes.extend_from_slice(&0xE8BDu16.to_le_bytes());
4749                bytes.extend_from_slice(&0x0FF0u16.to_le_bytes());
4750
4751                Ok(bytes)
4752            }
4753
4754            // === F32 VFP single-precision Thumb-2 encodings ===
4755            // VFP instruction words are identical to ARM32; emit as two LE halfwords.
4756            ArmOp::F32Add { sd, sn, sm } => {
4757                Ok(vfp_to_thumb_bytes(encode_vfp_3reg(0xEE300A00, sd, sn, sm)?))
4758            }
4759            ArmOp::F32Sub { sd, sn, sm } => {
4760                Ok(vfp_to_thumb_bytes(encode_vfp_3reg(0xEE300A40, sd, sn, sm)?))
4761            }
4762            ArmOp::F32Mul { sd, sn, sm } => {
4763                Ok(vfp_to_thumb_bytes(encode_vfp_3reg(0xEE200A00, sd, sn, sm)?))
4764            }
4765            ArmOp::F32Div { sd, sn, sm } => {
4766                Ok(vfp_to_thumb_bytes(encode_vfp_3reg(0xEE800A00, sd, sn, sm)?))
4767            }
4768            ArmOp::F32Abs { sd, sm } => {
4769                Ok(vfp_to_thumb_bytes(encode_vfp_2reg(0xEEB00AC0, sd, sm)?))
4770            }
4771            ArmOp::F32Neg { sd, sm } => {
4772                Ok(vfp_to_thumb_bytes(encode_vfp_2reg(0xEEB10A40, sd, sm)?))
4773            }
4774            ArmOp::F32Sqrt { sd, sm } => {
4775                Ok(vfp_to_thumb_bytes(encode_vfp_2reg(0xEEB10AC0, sd, sm)?))
4776            }
4777
4778            // f32 pseudo-ops — multi-instruction sequences
4779            // FPSCR RMode: 00=nearest, 01=+inf(ceil), 10=-inf(floor), 11=zero(trunc)
4780            ArmOp::F32Ceil { sd, sm } => self.encode_thumb_f32_rounding(sd, sm, 0b01),
4781            ArmOp::F32Floor { sd, sm } => self.encode_thumb_f32_rounding(sd, sm, 0b10),
4782            ArmOp::F32Trunc { sd, sm } => self.encode_thumb_f32_rounding(sd, sm, 0b11),
4783            ArmOp::F32Nearest { sd, sm } => self.encode_thumb_f32_rounding(sd, sm, 0b00),
4784            ArmOp::F32Min { sd, sn, sm } => self.encode_thumb_f32_minmax(sd, sn, sm, true),
4785            ArmOp::F32Max { sd, sn, sm } => self.encode_thumb_f32_minmax(sd, sn, sm, false),
4786            ArmOp::F32Copysign { sd, sn, sm } => self.encode_thumb_f32_copysign(sd, sn, sm),
4787
4788            // f32 comparisons — VCMP + VMRS + MOV #0 + IT + MOV #1
4789            ArmOp::F32Eq { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0x0),
4790            ArmOp::F32Ne { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0x1),
4791            ArmOp::F32Lt { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0x4),
4792            ArmOp::F32Le { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0x9),
4793            ArmOp::F32Gt { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0xC),
4794            ArmOp::F32Ge { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0xA),
4795
4796            ArmOp::F32Const { sd, value } => self.encode_thumb_f32_const(sd, *value),
4797
4798            ArmOp::F32Load { sd, addr } => {
4799                Ok(vfp_to_thumb_bytes(encode_vfp_ldst(0xED900A00, sd, addr)?))
4800            }
4801            ArmOp::F32Store { sd, addr } => {
4802                Ok(vfp_to_thumb_bytes(encode_vfp_ldst(0xED800A00, sd, addr)?))
4803            }
4804
4805            ArmOp::F32ConvertI32S { sd, rm } => self.encode_thumb_f32_convert_i32(sd, rm, true),
4806            ArmOp::F32ConvertI32U { sd, rm } => self.encode_thumb_f32_convert_i32(sd, rm, false),
4807            ArmOp::F32ConvertI64S { .. } | ArmOp::F32ConvertI64U { .. } => {
4808                Err(synth_core::Error::synthesis(
4809                    "F32 i64 conversion not supported (requires register pairs on 32-bit ARM)",
4810                ))
4811            }
4812            ArmOp::F32ReinterpretI32 { sd, rm } => {
4813                Ok(vfp_to_thumb_bytes(encode_vmov_core_sreg(true, sd, rm)?))
4814            }
4815            ArmOp::I32ReinterpretF32 { rd, sm } => {
4816                Ok(vfp_to_thumb_bytes(encode_vmov_core_sreg(false, sm, rd)?))
4817            }
4818            ArmOp::I32TruncF32S { rd, sm } => self.encode_thumb_i32_trunc_f32(rd, sm, true),
4819            ArmOp::I32TruncF32U { rd, sm } => self.encode_thumb_i32_trunc_f32(rd, sm, false),
4820
4821            // === F64 VFP double-precision Thumb-2 encodings ===
4822            // VFP instruction words are identical to ARM32; emit as two LE halfwords.
4823            ArmOp::F64Add { dd, dn, dm } => Ok(vfp_to_thumb_bytes(encode_vfp_3reg_f64(
4824                0xEE300B00, dd, dn, dm,
4825            )?)),
4826            ArmOp::F64Sub { dd, dn, dm } => Ok(vfp_to_thumb_bytes(encode_vfp_3reg_f64(
4827                0xEE300B40, dd, dn, dm,
4828            )?)),
4829            ArmOp::F64Mul { dd, dn, dm } => Ok(vfp_to_thumb_bytes(encode_vfp_3reg_f64(
4830                0xEE200B00, dd, dn, dm,
4831            )?)),
4832            ArmOp::F64Div { dd, dn, dm } => Ok(vfp_to_thumb_bytes(encode_vfp_3reg_f64(
4833                0xEE800B00, dd, dn, dm,
4834            )?)),
4835            ArmOp::F64Abs { dd, dm } => {
4836                Ok(vfp_to_thumb_bytes(encode_vfp_2reg_f64(0xEEB00BC0, dd, dm)?))
4837            }
4838            ArmOp::F64Neg { dd, dm } => {
4839                Ok(vfp_to_thumb_bytes(encode_vfp_2reg_f64(0xEEB10B40, dd, dm)?))
4840            }
4841            ArmOp::F64Sqrt { dd, dm } => {
4842                Ok(vfp_to_thumb_bytes(encode_vfp_2reg_f64(0xEEB10BC0, dd, dm)?))
4843            }
4844
4845            // f64 pseudo-ops
4846            // FPSCR RMode: 00=nearest, 01=+inf(ceil), 10=-inf(floor), 11=zero(trunc)
4847            ArmOp::F64Ceil { dd, dm } => self.encode_thumb_f64_rounding(dd, dm, 0b01),
4848            ArmOp::F64Floor { dd, dm } => self.encode_thumb_f64_rounding(dd, dm, 0b10),
4849            ArmOp::F64Trunc { dd, dm } => self.encode_thumb_f64_rounding(dd, dm, 0b11),
4850            ArmOp::F64Nearest { dd, dm } => self.encode_thumb_f64_rounding(dd, dm, 0b00),
4851            ArmOp::F64Min { dd, dn, dm } => self.encode_thumb_f64_minmax(dd, dn, dm, true),
4852            ArmOp::F64Max { dd, dn, dm } => self.encode_thumb_f64_minmax(dd, dn, dm, false),
4853            ArmOp::F64Copysign { dd, dn, dm } => self.encode_thumb_f64_copysign(dd, dn, dm),
4854
4855            // f64 comparisons
4856            ArmOp::F64Eq { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0x0),
4857            ArmOp::F64Ne { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0x1),
4858            ArmOp::F64Lt { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0x4),
4859            ArmOp::F64Le { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0x9),
4860            ArmOp::F64Gt { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0xC),
4861            ArmOp::F64Ge { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0xA),
4862
4863            ArmOp::F64Const { dd, value } => self.encode_thumb_f64_const(dd, *value),
4864
4865            ArmOp::F64Load { dd, addr } => Ok(vfp_to_thumb_bytes(encode_vfp_ldst_f64(
4866                0xED900B00, dd, addr,
4867            )?)),
4868            ArmOp::F64Store { dd, addr } => Ok(vfp_to_thumb_bytes(encode_vfp_ldst_f64(
4869                0xED800B00, dd, addr,
4870            )?)),
4871
4872            ArmOp::F64ConvertI32S { dd, rm } => self.encode_thumb_f64_convert_i32(dd, rm, true),
4873            ArmOp::F64ConvertI32U { dd, rm } => self.encode_thumb_f64_convert_i32(dd, rm, false),
4874            ArmOp::F64ConvertI64S { .. } | ArmOp::F64ConvertI64U { .. } => {
4875                Err(synth_core::Error::synthesis(
4876                    "F64 i64 conversion not supported (requires register pairs on 32-bit ARM)",
4877                ))
4878            }
4879            ArmOp::F64PromoteF32 { dd, sm } => self.encode_thumb_f64_promote_f32(dd, sm),
4880            ArmOp::F64ReinterpretI64 { dd, rmlo, rmhi } => Ok(vfp_to_thumb_bytes(
4881                encode_vmov_core_dreg(true, dd, rmlo, rmhi)?,
4882            )),
4883            ArmOp::I64ReinterpretF64 { rdlo, rdhi, dm } => Ok(vfp_to_thumb_bytes(
4884                encode_vmov_core_dreg(false, dm, rdlo, rdhi)?,
4885            )),
4886            ArmOp::I64TruncF64S { .. } | ArmOp::I64TruncF64U { .. } => {
4887                Err(synth_core::Error::synthesis(
4888                    "i64 truncation from F64 not supported (requires i64 register pairs on 32-bit ARM)",
4889                ))
4890            }
4891            ArmOp::I32TruncF64S { rd, dm } => self.encode_thumb_i32_trunc_f64(rd, dm, true),
4892            ArmOp::I32TruncF64U { rd, dm } => self.encode_thumb_i32_trunc_f64(rd, dm, false),
4893
4894            // ===== i64 operations: encode as multi-instruction Thumb-2 sequences =====
4895
4896            // I64Add: ADDS rdlo, rnlo, rmlo; ADC.W rdhi, rnhi, rmhi
4897            ArmOp::I64Add {
4898                rdlo,
4899                rdhi,
4900                rnlo,
4901                rnhi,
4902                rmlo,
4903                rmhi,
4904            } => {
4905                let mut bytes = Vec::new();
4906                // ADDS rdlo, rnlo, rmlo (16-bit)
4907                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Adds {
4908                    rd: *rdlo,
4909                    rn: *rnlo,
4910                    op2: Operand2::Reg(*rmlo),
4911                })?);
4912                // ADC.W rdhi, rnhi, rmhi (32-bit)
4913                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Adc {
4914                    rd: *rdhi,
4915                    rn: *rnhi,
4916                    op2: Operand2::Reg(*rmhi),
4917                })?);
4918                Ok(bytes)
4919            }
4920
4921            // I64Sub: SUBS rdlo, rnlo, rmlo; SBC.W rdhi, rnhi, rmhi
4922            ArmOp::I64Sub {
4923                rdlo,
4924                rdhi,
4925                rnlo,
4926                rnhi,
4927                rmlo,
4928                rmhi,
4929            } => {
4930                let mut bytes = Vec::new();
4931                // SUBS rdlo, rnlo, rmlo (16-bit)
4932                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Subs {
4933                    rd: *rdlo,
4934                    rn: *rnlo,
4935                    op2: Operand2::Reg(*rmlo),
4936                })?);
4937                // SBC.W rdhi, rnhi, rmhi (32-bit)
4938                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Sbc {
4939                    rd: *rdhi,
4940                    rn: *rnhi,
4941                    op2: Operand2::Reg(*rmhi),
4942                })?);
4943                Ok(bytes)
4944            }
4945
4946            // I64And: AND rdlo, rnlo, rmlo; AND rdhi, rnhi, rmhi
4947            ArmOp::I64And {
4948                rdlo,
4949                rdhi,
4950                rnlo,
4951                rnhi,
4952                rmlo,
4953                rmhi,
4954            } => {
4955                let mut bytes = Vec::new();
4956                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::And {
4957                    rd: *rdlo,
4958                    rn: *rnlo,
4959                    op2: Operand2::Reg(*rmlo),
4960                })?);
4961                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::And {
4962                    rd: *rdhi,
4963                    rn: *rnhi,
4964                    op2: Operand2::Reg(*rmhi),
4965                })?);
4966                Ok(bytes)
4967            }
4968
4969            // I64Or: ORR rdlo, rnlo, rmlo; ORR rdhi, rnhi, rmhi
4970            ArmOp::I64Or {
4971                rdlo,
4972                rdhi,
4973                rnlo,
4974                rnhi,
4975                rmlo,
4976                rmhi,
4977            } => {
4978                let mut bytes = Vec::new();
4979                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Orr {
4980                    rd: *rdlo,
4981                    rn: *rnlo,
4982                    op2: Operand2::Reg(*rmlo),
4983                })?);
4984                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Orr {
4985                    rd: *rdhi,
4986                    rn: *rnhi,
4987                    op2: Operand2::Reg(*rmhi),
4988                })?);
4989                Ok(bytes)
4990            }
4991
4992            // I64Xor: EOR rdlo, rnlo, rmlo; EOR rdhi, rnhi, rmhi
4993            ArmOp::I64Xor {
4994                rdlo,
4995                rdhi,
4996                rnlo,
4997                rnhi,
4998                rmlo,
4999                rmhi,
5000            } => {
5001                let mut bytes = Vec::new();
5002                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Eor {
5003                    rd: *rdlo,
5004                    rn: *rnlo,
5005                    op2: Operand2::Reg(*rmlo),
5006                })?);
5007                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Eor {
5008                    rd: *rdhi,
5009                    rn: *rnhi,
5010                    op2: Operand2::Reg(*rmhi),
5011                })?);
5012                Ok(bytes)
5013            }
5014
5015            // I64Eqz: ORR scratch, lo, hi; ITE EQ; MOV rd, #1; MOV rd, #0
5016            ArmOp::I64Eqz { rd, rnlo, rnhi } => self.encode_thumb(&ArmOp::I64SetCondZ {
5017                rd: *rd,
5018                rn_lo: *rnlo,
5019                rn_hi: *rnhi,
5020            }),
5021
5022            // I64 comparisons: delegate to I64SetCond
5023            ArmOp::I64Eq {
5024                rd,
5025                rnlo,
5026                rnhi,
5027                rmlo,
5028                rmhi,
5029            } => self.encode_thumb(&ArmOp::I64SetCond {
5030                rd: *rd,
5031                rn_lo: *rnlo,
5032                rn_hi: *rnhi,
5033                rm_lo: *rmlo,
5034                rm_hi: *rmhi,
5035                cond: synth_synthesis::Condition::EQ,
5036            }),
5037
5038            ArmOp::I64Ne {
5039                rd,
5040                rnlo,
5041                rnhi,
5042                rmlo,
5043                rmhi,
5044            } => self.encode_thumb(&ArmOp::I64SetCond {
5045                rd: *rd,
5046                rn_lo: *rnlo,
5047                rn_hi: *rnhi,
5048                rm_lo: *rmlo,
5049                rm_hi: *rmhi,
5050                cond: synth_synthesis::Condition::NE,
5051            }),
5052
5053            ArmOp::I64LtS {
5054                rd,
5055                rnlo,
5056                rnhi,
5057                rmlo,
5058                rmhi,
5059            } => self.encode_thumb(&ArmOp::I64SetCond {
5060                rd: *rd,
5061                rn_lo: *rnlo,
5062                rn_hi: *rnhi,
5063                rm_lo: *rmlo,
5064                rm_hi: *rmhi,
5065                cond: synth_synthesis::Condition::LT,
5066            }),
5067
5068            ArmOp::I64LtU {
5069                rd,
5070                rnlo,
5071                rnhi,
5072                rmlo,
5073                rmhi,
5074            } => self.encode_thumb(&ArmOp::I64SetCond {
5075                rd: *rd,
5076                rn_lo: *rnlo,
5077                rn_hi: *rnhi,
5078                rm_lo: *rmlo,
5079                rm_hi: *rmhi,
5080                cond: synth_synthesis::Condition::LO,
5081            }),
5082
5083            ArmOp::I64LeS {
5084                rd,
5085                rnlo,
5086                rnhi,
5087                rmlo,
5088                rmhi,
5089            } => self.encode_thumb(&ArmOp::I64SetCond {
5090                rd: *rd,
5091                rn_lo: *rnlo,
5092                rn_hi: *rnhi,
5093                rm_lo: *rmlo,
5094                rm_hi: *rmhi,
5095                cond: synth_synthesis::Condition::LE,
5096            }),
5097
5098            ArmOp::I64LeU {
5099                rd,
5100                rnlo,
5101                rnhi,
5102                rmlo,
5103                rmhi,
5104            } => self.encode_thumb(&ArmOp::I64SetCond {
5105                rd: *rd,
5106                rn_lo: *rnlo,
5107                rn_hi: *rnhi,
5108                rm_lo: *rmlo,
5109                rm_hi: *rmhi,
5110                cond: synth_synthesis::Condition::LS,
5111            }),
5112
5113            ArmOp::I64GtS {
5114                rd,
5115                rnlo,
5116                rnhi,
5117                rmlo,
5118                rmhi,
5119            } => self.encode_thumb(&ArmOp::I64SetCond {
5120                rd: *rd,
5121                rn_lo: *rnlo,
5122                rn_hi: *rnhi,
5123                rm_lo: *rmlo,
5124                rm_hi: *rmhi,
5125                cond: synth_synthesis::Condition::GT,
5126            }),
5127
5128            ArmOp::I64GtU {
5129                rd,
5130                rnlo,
5131                rnhi,
5132                rmlo,
5133                rmhi,
5134            } => self.encode_thumb(&ArmOp::I64SetCond {
5135                rd: *rd,
5136                rn_lo: *rnlo,
5137                rn_hi: *rnhi,
5138                rm_lo: *rmlo,
5139                rm_hi: *rmhi,
5140                cond: synth_synthesis::Condition::HI,
5141            }),
5142
5143            ArmOp::I64GeS {
5144                rd,
5145                rnlo,
5146                rnhi,
5147                rmlo,
5148                rmhi,
5149            } => self.encode_thumb(&ArmOp::I64SetCond {
5150                rd: *rd,
5151                rn_lo: *rnlo,
5152                rn_hi: *rnhi,
5153                rm_lo: *rmlo,
5154                rm_hi: *rmhi,
5155                cond: synth_synthesis::Condition::GE,
5156            }),
5157
5158            ArmOp::I64GeU {
5159                rd,
5160                rnlo,
5161                rnhi,
5162                rmlo,
5163                rmhi,
5164            } => self.encode_thumb(&ArmOp::I64SetCond {
5165                rd: *rd,
5166                rn_lo: *rnlo,
5167                rn_hi: *rnhi,
5168                rm_lo: *rmlo,
5169                rm_hi: *rmhi,
5170                cond: synth_synthesis::Condition::HS,
5171            }),
5172
5173            // I64Const: MOVW rdlo, lo16; MOVT rdlo, hi16; MOVW rdhi, lo16_hi; MOVT rdhi, hi16_hi
5174            ArmOp::I64Const { rdlo, rdhi, value } => {
5175                let lo32 = *value as u32;
5176                let hi32 = (*value >> 32) as u32;
5177                let mut bytes = Vec::new();
5178                // Load low 32 bits into rdlo
5179                bytes.extend_from_slice(
5180                    &self.encode_thumb32_movw_raw(reg_to_bits(rdlo), lo32 & 0xFFFF)?,
5181                );
5182                if lo32 > 0xFFFF {
5183                    bytes.extend_from_slice(
5184                        &self.encode_thumb32_movt_raw(reg_to_bits(rdlo), lo32 >> 16)?,
5185                    );
5186                }
5187                // Load high 32 bits into rdhi
5188                bytes.extend_from_slice(
5189                    &self.encode_thumb32_movw_raw(reg_to_bits(rdhi), hi32 & 0xFFFF)?,
5190                );
5191                if hi32 > 0xFFFF {
5192                    bytes.extend_from_slice(
5193                        &self.encode_thumb32_movt_raw(reg_to_bits(rdhi), hi32 >> 16)?,
5194                    );
5195                }
5196                Ok(bytes)
5197            }
5198
5199            // I64Ldr: LDR rdlo, [base, offset]; LDR rdhi, [base, offset+4]
5200            ArmOp::I64Ldr { rdlo, rdhi, addr } => {
5201                let mut bytes = Vec::new();
5202                let offset = if addr.offset < 0 {
5203                    0u32
5204                } else {
5205                    addr.offset as u32
5206                };
5207                bytes.extend_from_slice(&self.encode_thumb32_ldr(rdlo, &addr.base, offset)?);
5208                bytes.extend_from_slice(&self.encode_thumb32_ldr(
5209                    rdhi,
5210                    &addr.base,
5211                    offset.wrapping_add(4),
5212                )?);
5213                Ok(bytes)
5214            }
5215
5216            // I64Str: STR rdlo, [base, offset]; STR rdhi, [base, offset+4]
5217            ArmOp::I64Str { rdlo, rdhi, addr } => {
5218                let mut bytes = Vec::new();
5219                let offset = if addr.offset < 0 {
5220                    0u32
5221                } else {
5222                    addr.offset as u32
5223                };
5224                bytes.extend_from_slice(&self.encode_thumb32_str(rdlo, &addr.base, offset)?);
5225                bytes.extend_from_slice(&self.encode_thumb32_str(
5226                    rdhi,
5227                    &addr.base,
5228                    offset.wrapping_add(4),
5229                )?);
5230                Ok(bytes)
5231            }
5232
5233            // I64ExtendI32S: MOV rdlo, rn; ASR rdhi, rdlo, #31 (sign-extend)
5234            ArmOp::I64ExtendI32S { rdlo, rdhi, rn } => {
5235                let mut bytes = Vec::new();
5236                if rdlo != rn {
5237                    // MOV rdlo, rn (16-bit)
5238                    bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Mov {
5239                        rd: *rdlo,
5240                        op2: Operand2::Reg(*rn),
5241                    })?);
5242                }
5243                // ASR rdhi, rdlo, #31 (sign-extend: fill high word with sign bit)
5244                bytes.extend_from_slice(
5245                    &self.encode_thumb32_shift(rdhi, rdlo, 31, 0b10)?, // ASR type
5246                );
5247                Ok(bytes)
5248            }
5249
5250            // I64ExtendI32U: MOV rdlo, rn; MOV rdhi, #0
5251            ArmOp::I64ExtendI32U { rdlo, rdhi, rn } => {
5252                let mut bytes = Vec::new();
5253                if rdlo != rn {
5254                    // MOV rdlo, rn
5255                    bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Mov {
5256                        rd: *rdlo,
5257                        op2: Operand2::Reg(*rn),
5258                    })?);
5259                }
5260                // MOV rdhi, #0 (16-bit: MOVS Rd, #0)
5261                let rdhi_bits = reg_to_bits(rdhi) as u16;
5262                let instr: u16 = 0x2000 | (rdhi_bits << 8);
5263                bytes.extend_from_slice(&instr.to_le_bytes());
5264                Ok(bytes)
5265            }
5266
5267            // I32WrapI64: MOV rd, rnlo (just take low 32 bits)
5268            ArmOp::I32WrapI64 { rd, rnlo } => {
5269                if rd == rnlo {
5270                    // No-op: already in the right register
5271                    let instr: u16 = 0xBF00; // NOP
5272                    Ok(instr.to_le_bytes().to_vec())
5273                } else {
5274                    // MOV rd, rnlo
5275                    self.encode_thumb(&ArmOp::Mov {
5276                        rd: *rd,
5277                        op2: Operand2::Reg(*rnlo),
5278                    })
5279                }
5280            }
5281
5282            // ===== Helium MVE operations (Thumb-2 encoding) =====
5283            ArmOp::MveLoad { qd, addr } => Ok(vfp_to_thumb_bytes(encode_mve_vldrw(qd, addr))),
5284            ArmOp::MveStore { qd, addr } => Ok(vfp_to_thumb_bytes(encode_mve_vstrw(qd, addr))),
5285            ArmOp::MveConst { qd, bytes } => self.encode_thumb_mve_const(qd, bytes),
5286            ArmOp::MveAnd { qd, qn, qm } => Ok(vfp_to_thumb_bytes(encode_mve_3reg_bitwise(
5287                0xEF000150, qd, qn, qm,
5288            ))),
5289            ArmOp::MveOrr { qd, qn, qm } => Ok(vfp_to_thumb_bytes(encode_mve_3reg_bitwise(
5290                0xEF200150, qd, qn, qm,
5291            ))),
5292            ArmOp::MveEor { qd, qn, qm } => Ok(vfp_to_thumb_bytes(encode_mve_3reg_bitwise(
5293                0xFF000150, qd, qn, qm,
5294            ))),
5295            ArmOp::MveMvn { qd, qm } => {
5296                // VMVN Qd, Qm: 0xFFB005C0 | Qd<<12 | Qm
5297                let qd_enc = qreg_to_num(qd);
5298                let qm_enc = qreg_to_num(qm);
5299                let instr: u32 = 0xFFB005C0 | ((qd_enc * 2) << 12) | (qm_enc * 2);
5300                Ok(vfp_to_thumb_bytes(instr))
5301            }
5302            ArmOp::MveBic { qd, qn, qm } => Ok(vfp_to_thumb_bytes(encode_mve_3reg_bitwise(
5303                0xEF100150, qd, qn, qm,
5304            ))),
5305            ArmOp::MveAddI { qd, qn, qm, size } => {
5306                let sz = mve_size_bits(size);
5307                let base: u32 = 0xEF000840 | (sz << 20);
5308                Ok(vfp_to_thumb_bytes(encode_mve_3reg(base, qd, qn, qm)))
5309            }
5310            ArmOp::MveSubI { qd, qn, qm, size } => {
5311                let sz = mve_size_bits(size);
5312                let base: u32 = 0xFF000840 | (sz << 20);
5313                Ok(vfp_to_thumb_bytes(encode_mve_3reg(base, qd, qn, qm)))
5314            }
5315            ArmOp::MveMulI { qd, qn, qm, size } => {
5316                let sz = mve_size_bits(size);
5317                let base: u32 = 0xEF000950 | (sz << 20);
5318                Ok(vfp_to_thumb_bytes(encode_mve_3reg(base, qd, qn, qm)))
5319            }
5320            ArmOp::MveNegI { qd, qm, size } => {
5321                let sz = mve_size_bits(size);
5322                // VNEG.Sx Qd, Qm
5323                let qd_enc = qreg_to_num(qd);
5324                let qm_enc = qreg_to_num(qm);
5325                let base: u32 = 0xFFB103C0 | (sz << 18);
5326                let instr = base | ((qd_enc * 2) << 12) | (qm_enc * 2);
5327                Ok(vfp_to_thumb_bytes(instr))
5328            }
5329            ArmOp::MveDup { qd, rn, size } => {
5330                let sz = mve_size_bits(size);
5331                let qd_enc = qreg_to_num(qd);
5332                let rn_bits = reg_to_bits(rn);
5333                // VDUP.sz Qd, Rn: EEA0 0B10 variant
5334                // size encoding: 00=32, 01=16, 10=8
5335                let be = match sz {
5336                    0 => 0b00u32, // 8-bit
5337                    1 => 0b01,    // 16-bit
5338                    _ => 0b00,    // 32-bit (default)
5339                };
5340                let instr: u32 = 0xEEA00B10 | ((qd_enc * 2) << 16) | (rn_bits << 12) | (be << 5);
5341                Ok(vfp_to_thumb_bytes(instr))
5342            }
5343            ArmOp::MveExtractLane { rd, qn, lane, size } => {
5344                let qn_enc = qreg_to_num(qn);
5345                let rd_bits = reg_to_bits(rd);
5346                // VMOV.sz Rd, Dn[x] — extract from Q-register lane
5347                // For 32-bit: VMOV Rd, Dn — where Dn is the appropriate D-register
5348                let d_reg = qn_enc * 2 + ((*lane as u32) >> 1);
5349                let lane_in_d = (*lane as u32) & 1;
5350                let _sz = mve_size_bits(size);
5351                // VMOV Rd, Dn[x]: EE10 0B10 for 32-bit
5352                let instr: u32 = 0xEE100B10 | (d_reg << 16) | (rd_bits << 12) | (lane_in_d << 21);
5353                Ok(vfp_to_thumb_bytes(instr))
5354            }
5355            ArmOp::MveInsertLane { qd, rn, lane, size } => {
5356                let qd_enc = qreg_to_num(qd);
5357                let rn_bits = reg_to_bits(rn);
5358                let d_reg = qd_enc * 2 + ((*lane as u32) >> 1);
5359                let lane_in_d = (*lane as u32) & 1;
5360                let _sz = mve_size_bits(size);
5361                // VMOV Dn[x], Rn: EE00 0B10 for 32-bit
5362                let instr: u32 = 0xEE000B10 | (d_reg << 16) | (rn_bits << 12) | (lane_in_d << 21);
5363                Ok(vfp_to_thumb_bytes(instr))
5364            }
5365
5366            // MVE float comparisons — emit VCMP + VPSEL sequence (simplified: just VCMP)
5367            ArmOp::MveCmpEqI { qd, qn, qm, size }
5368            | ArmOp::MveCmpNeI { qd, qn, qm, size }
5369            | ArmOp::MveCmpLtS { qd, qn, qm, size }
5370            | ArmOp::MveCmpLtU { qd, qn, qm, size }
5371            | ArmOp::MveCmpGtS { qd, qn, qm, size }
5372            | ArmOp::MveCmpGtU { qd, qn, qm, size }
5373            | ArmOp::MveCmpLeS { qd, qn, qm, size }
5374            | ArmOp::MveCmpLeU { qd, qn, qm, size }
5375            | ArmOp::MveCmpGeS { qd, qn, qm, size }
5376            | ArmOp::MveCmpGeU { qd, qn, qm, size } => {
5377                // Encode as VADD (placeholder encoding — real implementation
5378                // would use VCMP + VPSEL pair)
5379                let sz = mve_size_bits(size);
5380                let base: u32 = 0xEF000840 | (sz << 20);
5381                Ok(vfp_to_thumb_bytes(encode_mve_3reg(base, qd, qn, qm)))
5382            }
5383
5384            // f32x4 MVE arithmetic
5385            ArmOp::MveAddF32 { qd, qn, qm } => {
5386                // VADD.F32 Qd, Qn, Qm (MVE): 0xEF000D40
5387                Ok(vfp_to_thumb_bytes(encode_mve_3reg(0xEF000D40, qd, qn, qm)))
5388            }
5389            ArmOp::MveSubF32 { qd, qn, qm } => {
5390                // VSUB.F32 Qd, Qn, Qm (MVE): 0xEF200D40
5391                Ok(vfp_to_thumb_bytes(encode_mve_3reg(0xEF200D40, qd, qn, qm)))
5392            }
5393            ArmOp::MveMulF32 { qd, qn, qm } => {
5394                // VMUL.F32 Qd, Qn, Qm (MVE): 0xFF000D50
5395                Ok(vfp_to_thumb_bytes(encode_mve_3reg(0xFF000D50, qd, qn, qm)))
5396            }
5397            ArmOp::MveNegF32 { qd, qm } => {
5398                let qd_enc = qreg_to_num(qd);
5399                let qm_enc = qreg_to_num(qm);
5400                // VNEG.F32 Qd, Qm: FFB907C0
5401                let instr: u32 = 0xFFB907C0 | ((qd_enc * 2) << 12) | (qm_enc * 2);
5402                Ok(vfp_to_thumb_bytes(instr))
5403            }
5404            ArmOp::MveAbsF32 { qd, qm } => {
5405                let qd_enc = qreg_to_num(qd);
5406                let qm_enc = qreg_to_num(qm);
5407                // VABS.F32 Qd, Qm: FFB90740
5408                let instr: u32 = 0xFFB90740 | ((qd_enc * 2) << 12) | (qm_enc * 2);
5409                Ok(vfp_to_thumb_bytes(instr))
5410            }
5411            ArmOp::MveCmpEqF32 { qd, qn, qm }
5412            | ArmOp::MveCmpNeF32 { qd, qn, qm }
5413            | ArmOp::MveCmpLtF32 { qd, qn, qm }
5414            | ArmOp::MveCmpLeF32 { qd, qn, qm }
5415            | ArmOp::MveCmpGtF32 { qd, qn, qm }
5416            | ArmOp::MveCmpGeF32 { qd, qn, qm } => {
5417                // Placeholder: encode as VADD.F32 (real impl needs VCMP.F32 + VPSEL)
5418                Ok(vfp_to_thumb_bytes(encode_mve_3reg(0xEF000D40, qd, qn, qm)))
5419            }
5420            ArmOp::MveDupF32 { qd, rn } => {
5421                let qd_enc = qreg_to_num(qd);
5422                let rn_bits = reg_to_bits(rn);
5423                // VDUP.32 Qd, Rn (same encoding as integer VDUP.32)
5424                let instr: u32 = 0xEEA00B10 | ((qd_enc * 2) << 16) | (rn_bits << 12);
5425                Ok(vfp_to_thumb_bytes(instr))
5426            }
5427            ArmOp::MveExtractLaneF32 { rd, qn, lane } => {
5428                let qn_enc = qreg_to_num(qn);
5429                let rd_bits = reg_to_bits(rd);
5430                // VMOV Rd, Sn where Sn = Q*4 + lane
5431                let s_num = qn_enc * 4 + (*lane as u32);
5432                let (vn, n) = encode_sreg(s_num);
5433                let instr: u32 = 0xEE100A10 | (vn << 16) | (rd_bits << 12) | (n << 7);
5434                Ok(vfp_to_thumb_bytes(instr))
5435            }
5436            ArmOp::MveReplaceLaneF32 { qd, rn, lane } => {
5437                let qd_enc = qreg_to_num(qd);
5438                let rn_bits = reg_to_bits(rn);
5439                // VMOV Sn, Rn where Sn = Q*4 + lane
5440                let s_num = qd_enc * 4 + (*lane as u32);
5441                let (vn, n) = encode_sreg(s_num);
5442                let instr: u32 = 0xEE000A10 | (vn << 16) | (rn_bits << 12) | (n << 7);
5443                Ok(vfp_to_thumb_bytes(instr))
5444            }
5445            ArmOp::MveDivF32 { qd, qn, qm } => {
5446                // Lane-wise: extract 4 S-regs, VDIV, insert back
5447                self.encode_thumb_mve_lane_wise_f32_binop(qd, qn, qm, 0xEE800A00)
5448            }
5449            ArmOp::MveSqrtF32 { qd, qm } => {
5450                // Lane-wise: extract 4 S-regs, VSQRT, insert back
5451                self.encode_thumb_mve_lane_wise_f32_sqrt(qd, qm)
5452            }
5453
5454            // Catch-all for any remaining ops
5455            _ => {
5456                let instr: u16 = 0xBF00; // NOP
5457                Ok(instr.to_le_bytes().to_vec())
5458            }
5459        }
5460    }
5461
5462    // === Thumb-2 VFP multi-instruction helpers ===
5463
5464    /// Encode F32 comparison as Thumb-2: VCMP.F32 + VMRS + MOVS rd,#0 + IT + MOV rd,#1
5465    fn encode_thumb_f32_compare(
5466        &self,
5467        rd: &Reg,
5468        sn: &VfpReg,
5469        sm: &VfpReg,
5470        cond_code: u32,
5471    ) -> Result<Vec<u8>> {
5472        let mut bytes = Vec::new();
5473        let rd_bits = reg_to_bits(rd);
5474
5475        // VCMP.F32 Sn, Sm
5476        let sn_num = vfp_sreg_to_num(sn)?;
5477        let sm_num = vfp_sreg_to_num(sm)?;
5478        let (vd, d) = encode_sreg(sn_num);
5479        let (vm, m) = encode_sreg(sm_num);
5480        let vcmp = 0xEEB40A40 | (d << 22) | (vd << 12) | (m << 5) | vm;
5481        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcmp));
5482
5483        // VMRS APSR_nzcv, FPSCR: 0xEEF1FA10
5484        bytes.extend_from_slice(&vfp_to_thumb_bytes(0xEEF1FA10));
5485
5486        // MOVS Rd, #0 (16-bit): 0010 0 Rd(3) 0000 0000
5487        if rd_bits < 8 {
5488            let movs_zero: u16 = 0x2000 | ((rd_bits as u16) << 8);
5489            bytes.extend_from_slice(&movs_zero.to_le_bytes());
5490        } else {
5491            // MOV.W Rd, #0 (32-bit Thumb-2)
5492            let hw1: u16 = 0xF04F;
5493            let hw2: u16 = (rd_bits as u16) << 8;
5494            bytes.extend_from_slice(&hw1.to_le_bytes());
5495            bytes.extend_from_slice(&hw2.to_le_bytes());
5496        }
5497
5498        // IT<cond> — If-Then for conditional MOV
5499        // IT encoding: 1011 1111 cond(4) mask(4)
5500        // mask = 0x8 for single "then" (IT)
5501        let it: u16 = 0xBF00 | ((cond_code as u16) << 4) | 0x8;
5502        bytes.extend_from_slice(&it.to_le_bytes());
5503
5504        // MOV Rd, #1 (16-bit, conditional due to IT): 0010 0 Rd(3) 0000 0001
5505        if rd_bits < 8 {
5506            let mov_one: u16 = 0x2001 | ((rd_bits as u16) << 8);
5507            bytes.extend_from_slice(&mov_one.to_le_bytes());
5508        } else {
5509            // MOV.W Rd, #1 (32-bit)
5510            let hw1: u16 = 0xF04F;
5511            let hw2: u16 = ((rd_bits as u16) << 8) | 0x01;
5512            bytes.extend_from_slice(&hw1.to_le_bytes());
5513            bytes.extend_from_slice(&hw2.to_le_bytes());
5514        }
5515
5516        Ok(bytes)
5517    }
5518
5519    /// Encode F32 constant load as Thumb-2: MOVW + MOVT + VMOV
5520    fn encode_thumb_f32_const(&self, sd: &VfpReg, value: f32) -> Result<Vec<u8>> {
5521        let mut bytes = Vec::new();
5522        let bits = value.to_bits();
5523        let rt: u32 = 12; // R12/IP as temp
5524
5525        // MOVW R12, #lo16
5526        // Thumb-2 MOVW: 11110 i 10 0100 imm4 | 0 imm3 Rd imm8
5527        let lo16 = bits & 0xFFFF;
5528        let imm4 = (lo16 >> 12) & 0xF;
5529        let i_bit = (lo16 >> 11) & 1;
5530        let imm3 = (lo16 >> 8) & 0x7;
5531        let imm8 = lo16 & 0xFF;
5532        let hw1: u16 = (0xF240 | (i_bit << 10) | imm4) as u16;
5533        let hw2: u16 = ((imm3 << 12) | (rt << 8) | imm8) as u16;
5534        bytes.extend_from_slice(&hw1.to_le_bytes());
5535        bytes.extend_from_slice(&hw2.to_le_bytes());
5536
5537        // MOVT R12, #hi16
5538        let hi16 = (bits >> 16) & 0xFFFF;
5539        let imm4 = (hi16 >> 12) & 0xF;
5540        let i_bit = (hi16 >> 11) & 1;
5541        let imm3 = (hi16 >> 8) & 0x7;
5542        let imm8 = hi16 & 0xFF;
5543        let hw1: u16 = (0xF2C0 | (i_bit << 10) | imm4) as u16;
5544        let hw2: u16 = ((imm3 << 12) | (rt << 8) | imm8) as u16;
5545        bytes.extend_from_slice(&hw1.to_le_bytes());
5546        bytes.extend_from_slice(&hw2.to_le_bytes());
5547
5548        // VMOV Sd, R12
5549        let vmov = encode_vmov_core_sreg(true, sd, &Reg::R12)?;
5550        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
5551
5552        Ok(bytes)
5553    }
5554
5555    /// Encode VMOV + VCVT.F32.xS32 as Thumb-2
5556    fn encode_thumb_f32_convert_i32(&self, sd: &VfpReg, rm: &Reg, signed: bool) -> Result<Vec<u8>> {
5557        let mut bytes = Vec::new();
5558
5559        // VMOV Sd, Rm
5560        let vmov = encode_vmov_core_sreg(true, sd, rm)?;
5561        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
5562
5563        // VCVT.F32.S32/U32 Sd, Sd
5564        let sd_num = vfp_sreg_to_num(sd)?;
5565        let (vd, d) = encode_sreg(sd_num);
5566        let (vm, m) = encode_sreg(sd_num);
5567        let base = if signed { 0xEEB80A40 } else { 0xEEB80AC0 };
5568        let vcvt = base | (d << 22) | (vd << 12) | (m << 5) | vm;
5569        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt));
5570
5571        Ok(bytes)
5572    }
5573
5574    /// Encode F32 rounding pseudo-op as Thumb-2 via VCVT to integer and back
5575    /// Encode F32 rounding as Thumb-2.
5576    /// `mode`: FPSCR RMode — 0b00=nearest, 0b01=+inf(ceil), 0b10=-inf(floor), 0b11=zero(trunc)
5577    ///
5578    /// For trunc: uses VCVTR.S32.F32 (always truncates).
5579    /// For ceil/floor/nearest: sets FPSCR rounding mode, uses VCVT.S32.F32 (non-R variant),
5580    /// then restores FPSCR.
5581    fn encode_thumb_f32_rounding(&self, sd: &VfpReg, sm: &VfpReg, mode: u8) -> Result<Vec<u8>> {
5582        let mut bytes = Vec::new();
5583        let sm_num = vfp_sreg_to_num(sm)?;
5584        let sd_num = vfp_sreg_to_num(sd)?;
5585        let (vd_s, d_s) = encode_sreg(sd_num);
5586        let (vm_s, m_s) = encode_sreg(sm_num);
5587
5588        if mode == 0b11 {
5589            // Trunc (toward zero): VCVTR.S32.F32 — bit[7]=1, always truncates
5590            let vcvt_to_int = 0xEEBD0AC0 | (d_s << 22) | (vd_s << 12) | (m_s << 5) | vm_s;
5591            bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_int));
5592        } else {
5593            // ceil/floor/nearest: manipulate FPSCR rounding mode
5594            let rt: u32 = 12; // R12/IP as temp
5595
5596            // VMRS R12, FPSCR
5597            let vmrs = 0xEEF10A10 | (rt << 12);
5598            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmrs));
5599
5600            // BIC.W R12, R12, #(3 << 22) — clear RMode bits [23:22]
5601            // Thumb-2 modified immediate for 3<<22 = 0x00C00000:
5602            // BIC.W encoding: 11110 i 0 0001 S Rn | 0 imm3 Rd imm8
5603            // 0x00C00000 = 0x03 shifted left by 22 => Thumb mod-imm: i=0, imm3=0b101, imm8=0x03
5604            let bic_hw1: u16 = 0xF020 | ((rt as u16) & 0xF); // BIC, Rn=R12
5605            let bic_hw2: u16 = (0x05 << 12) | ((rt as u16) << 8) | 0x03;
5606            bytes.extend_from_slice(&bic_hw1.to_le_bytes());
5607            bytes.extend_from_slice(&bic_hw2.to_le_bytes());
5608
5609            // ORR.W R12, R12, #(mode << 22)
5610            if mode != 0 {
5611                let orr_hw1: u16 = 0xF040 | ((rt as u16) & 0xF); // ORR, Rn=R12
5612                let orr_hw2: u16 = (0x05 << 12) | ((rt as u16) << 8) | (mode as u16);
5613                bytes.extend_from_slice(&orr_hw1.to_le_bytes());
5614                bytes.extend_from_slice(&orr_hw2.to_le_bytes());
5615            }
5616
5617            // VMSR FPSCR, R12
5618            let vmsr = 0xEEE10A10 | (rt << 12);
5619            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmsr));
5620
5621            // VCVT.S32.F32 Sd, Sm — non-R variant (bit[7]=0), uses FPSCR rmode
5622            let vcvt_to_int = 0xEEBD0A40 | (d_s << 22) | (vd_s << 12) | (m_s << 5) | vm_s;
5623            bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_int));
5624
5625            // Restore FPSCR: clear rmode bits back to nearest (default)
5626            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmrs));
5627            bytes.extend_from_slice(&bic_hw1.to_le_bytes());
5628            bytes.extend_from_slice(&bic_hw2.to_le_bytes());
5629            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmsr));
5630        }
5631
5632        // VCVT.F32.S32 Sd, Sd (convert integer result back to float)
5633        let (vd2, d2) = encode_sreg(sd_num);
5634        let vcvt_to_float = 0xEEB80A40 | (d2 << 22) | (vd2 << 12) | (d_s << 5) | vd_s;
5635        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_float));
5636
5637        Ok(bytes)
5638    }
5639
5640    /// Encode F32 min/max as Thumb-2: VMOV + VCMP + VMRS + IT + VMOV
5641    fn encode_thumb_f32_minmax(
5642        &self,
5643        sd: &VfpReg,
5644        sn: &VfpReg,
5645        sm: &VfpReg,
5646        is_min: bool,
5647    ) -> Result<Vec<u8>> {
5648        let mut bytes = Vec::new();
5649        let sn_num = vfp_sreg_to_num(sn)?;
5650        let sm_num = vfp_sreg_to_num(sm)?;
5651        let sd_num = vfp_sreg_to_num(sd)?;
5652
5653        // VMOV.F32 Sd, Sn
5654        let (vd, d) = encode_sreg(sd_num);
5655        let (vn, n) = encode_sreg(sn_num);
5656        let vmov_sn = 0xEEB00A40 | (d << 22) | (vd << 12) | (n << 5) | vn;
5657        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov_sn));
5658
5659        // VCMP.F32 Sn, Sm
5660        let (vm, m) = encode_sreg(sm_num);
5661        let vcmp = 0xEEB40A40 | (n << 22) | (vn << 12) | (m << 5) | vm;
5662        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcmp));
5663
5664        // VMRS APSR_nzcv, FPSCR
5665        bytes.extend_from_slice(&vfp_to_thumb_bytes(0xEEF1FA10));
5666
5667        // IT GT (for min) or IT MI (for max)
5668        let cond: u16 = if is_min { 0xC } else { 0x4 };
5669        let it: u16 = 0xBF00 | (cond << 4) | 0x8;
5670        bytes.extend_from_slice(&it.to_le_bytes());
5671
5672        // VMOV{cond}.F32 Sd, Sm — conditional VMOV in IT block
5673        let vmov_sm = 0xEEB00A40 | (d << 22) | (vd << 12) | (m << 5) | vm;
5674        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov_sm));
5675
5676        Ok(bytes)
5677    }
5678
5679    /// Encode F32 copysign as Thumb-2
5680    fn encode_thumb_f32_copysign(&self, sd: &VfpReg, sn: &VfpReg, sm: &VfpReg) -> Result<Vec<u8>> {
5681        let mut bytes = Vec::new();
5682
5683        // VMOV R12, Sm (get sign source bits)
5684        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_sreg(
5685            false,
5686            sm,
5687            &Reg::R12,
5688        )?));
5689
5690        // VMOV R0, Sn (get magnitude source bits)
5691        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_sreg(
5692            false,
5693            sn,
5694            &Reg::R0,
5695        )?));
5696
5697        // AND.W R12, R12, #0x80000000
5698        // Thumb-2 modified immediate: 0x80000000 = constant 0x80 with rotation
5699        // Using T1 encoding: 11110 i 0 0000 S Rn | 0 imm3 Rd imm8
5700        // 0x80000000: i=0, imm3=0b001, imm8=0x00 (rotation=4, value=0x80)
5701        // Actually encoding #0x80000000 as modified constant:
5702        // bit pattern 1 followed by 31 zeros: enc = 0b0100_00000000 = 0x0100? No.
5703        // ARM modified immediate: abcdefgh rotated. 0x80000000 = 0x80 ROR 2 = enc 0x0102
5704        // Actually: value = abcdefgh ROR (2*rot). 0x80 = 10000000, ROR 2 gives 0x20000000.
5705        // For 0x80000000: 0x02 ROR 2 = 0x80000000. So imm12 = (1<<8) | 0x02 = 0x102
5706        let hw1: u16 = 0xF000 | 12; // AND.W R12, R12, #modified_const (i=0, Rn=R12)
5707        let hw2: u16 = (0x1 << 12) | (12 << 8) | 0x02; // imm3=1, Rd=R12, imm8=0x02
5708        bytes.extend_from_slice(&hw1.to_le_bytes());
5709        bytes.extend_from_slice(&hw2.to_le_bytes());
5710
5711        // BIC.W R0, R0, #0x80000000 (R0 = register 0, fields are zero)
5712        let hw1: u16 = 0xF020; // BIC.W R0, R0, #modified_const (i=0, Rn=R0)
5713        let hw2: u16 = (0x1 << 12) | 0x02; // imm3=1, Rd=R0, imm8=0x02
5714        bytes.extend_from_slice(&hw1.to_le_bytes());
5715        bytes.extend_from_slice(&hw2.to_le_bytes());
5716
5717        // ORR.W R0, R0, R12 (R0 = register 0)
5718        let hw1: u16 = 0xEA40; // ORR.W R0, R0, R12 (Rn=R0)
5719        let hw2: u16 = 12; // Rd=R0, Rm=R12
5720        bytes.extend_from_slice(&hw1.to_le_bytes());
5721        bytes.extend_from_slice(&hw2.to_le_bytes());
5722
5723        // VMOV Sd, R0
5724        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_sreg(
5725            true,
5726            sd,
5727            &Reg::R0,
5728        )?));
5729
5730        Ok(bytes)
5731    }
5732
5733    /// Encode F64 comparison as Thumb-2: VCMP.F64 + VMRS + MOV #0 + IT + MOV #1
5734    fn encode_thumb_f64_compare(
5735        &self,
5736        rd: &Reg,
5737        dn: &VfpReg,
5738        dm: &VfpReg,
5739        cond_code: u32,
5740    ) -> Result<Vec<u8>> {
5741        let mut bytes = Vec::new();
5742        let rd_bits = reg_to_bits(rd);
5743
5744        // VCMP.F64 Dn, Dm
5745        let dn_num = vfp_dreg_to_num(dn)?;
5746        let dm_num = vfp_dreg_to_num(dm)?;
5747        let (vd, d) = encode_dreg(dn_num);
5748        let (vm, m) = encode_dreg(dm_num);
5749        let vcmp = 0xEEB40B40 | (d << 22) | (vd << 12) | (m << 5) | vm;
5750        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcmp));
5751
5752        // VMRS APSR_nzcv, FPSCR
5753        bytes.extend_from_slice(&vfp_to_thumb_bytes(0xEEF1FA10));
5754
5755        // MOVS Rd, #0
5756        if rd_bits < 8 {
5757            let movs_zero: u16 = 0x2000 | ((rd_bits as u16) << 8);
5758            bytes.extend_from_slice(&movs_zero.to_le_bytes());
5759        } else {
5760            let hw1: u16 = 0xF04F;
5761            let hw2: u16 = (rd_bits as u16) << 8;
5762            bytes.extend_from_slice(&hw1.to_le_bytes());
5763            bytes.extend_from_slice(&hw2.to_le_bytes());
5764        }
5765
5766        // IT<cond>
5767        let it: u16 = 0xBF00 | ((cond_code as u16) << 4) | 0x8;
5768        bytes.extend_from_slice(&it.to_le_bytes());
5769
5770        // MOV Rd, #1
5771        if rd_bits < 8 {
5772            let mov_one: u16 = 0x2001 | ((rd_bits as u16) << 8);
5773            bytes.extend_from_slice(&mov_one.to_le_bytes());
5774        } else {
5775            let hw1: u16 = 0xF04F;
5776            let hw2: u16 = ((rd_bits as u16) << 8) | 0x01;
5777            bytes.extend_from_slice(&hw1.to_le_bytes());
5778            bytes.extend_from_slice(&hw2.to_le_bytes());
5779        }
5780
5781        Ok(bytes)
5782    }
5783
5784    /// Encode F64 constant load as Thumb-2: MOVW+MOVT (lo32 into R0) + MOVW+MOVT (hi32 into R12) + VMOV Dd, R0, R12
5785    fn encode_thumb_f64_const(&self, dd: &VfpReg, value: f64) -> Result<Vec<u8>> {
5786        let mut bytes = Vec::new();
5787        let bits = value.to_bits();
5788        let lo32 = bits as u32;
5789        let hi32 = (bits >> 32) as u32;
5790
5791        // MOVW R0, #lo16(lo32)
5792        let lo16 = lo32 & 0xFFFF;
5793        bytes.extend_from_slice(&self.encode_thumb32_movw_raw(0, lo16)?);
5794
5795        // MOVT R0, #hi16(lo32)
5796        let hi16 = (lo32 >> 16) & 0xFFFF;
5797        bytes.extend_from_slice(&self.encode_thumb32_movt_raw(0, hi16)?);
5798
5799        // MOVW R12, #lo16(hi32)
5800        let lo16 = hi32 & 0xFFFF;
5801        bytes.extend_from_slice(&self.encode_thumb32_movw_raw(12, lo16)?);
5802
5803        // MOVT R12, #hi16(hi32)
5804        let hi16 = (hi32 >> 16) & 0xFFFF;
5805        bytes.extend_from_slice(&self.encode_thumb32_movt_raw(12, hi16)?);
5806
5807        // VMOV Dd, R0, R12
5808        let vmov = encode_vmov_core_dreg(true, dd, &Reg::R0, &Reg::R12)?;
5809        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
5810
5811        Ok(bytes)
5812    }
5813
5814    /// Encode VMOV Sd, Rm + VCVT.F64.S32/U32 Dd, Sd as Thumb-2
5815    fn encode_thumb_f64_convert_i32(&self, dd: &VfpReg, rm: &Reg, signed: bool) -> Result<Vec<u8>> {
5816        let mut bytes = Vec::new();
5817
5818        // VMOV S0, Rm
5819        let vmov = encode_vmov_core_sreg(true, &VfpReg::S0, rm)?;
5820        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
5821
5822        // VCVT.F64.S32 Dd, S0 or VCVT.F64.U32 Dd, S0
5823        let dd_num = vfp_dreg_to_num(dd)?;
5824        let (vd, d) = encode_dreg(dd_num);
5825        let base = if signed { 0xEEB80B40 } else { 0xEEB80BC0 };
5826        let vcvt = base | (d << 22) | (vd << 12);
5827        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt));
5828
5829        Ok(bytes)
5830    }
5831
5832    /// Encode VCVT.F64.F32 Dd, Sm as Thumb-2
5833    fn encode_thumb_f64_promote_f32(&self, dd: &VfpReg, sm: &VfpReg) -> Result<Vec<u8>> {
5834        let dd_num = vfp_dreg_to_num(dd)?;
5835        let sm_num = vfp_sreg_to_num(sm)?;
5836        let (vd, d) = encode_dreg(dd_num);
5837        let (vm, m) = encode_sreg(sm_num);
5838
5839        let vcvt = 0xEEB70AC0 | (d << 22) | (vd << 12) | (m << 5) | vm;
5840        Ok(vfp_to_thumb_bytes(vcvt))
5841    }
5842
5843    /// Encode VCVT.S32/U32.F64 S0, Dm + VMOV Rd, S0 as Thumb-2
5844    fn encode_thumb_i32_trunc_f64(&self, rd: &Reg, dm: &VfpReg, signed: bool) -> Result<Vec<u8>> {
5845        let mut bytes = Vec::new();
5846        let dm_num = vfp_dreg_to_num(dm)?;
5847        let (vm, m) = encode_dreg(dm_num);
5848
5849        // VCVT.S32.F64 S0, Dm or VCVT.U32.F64 S0, Dm
5850        let base = if signed { 0xEEBD0BC0 } else { 0xEEBC0BC0 };
5851        let vcvt = base | (m << 5) | vm;
5852        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt));
5853
5854        // VMOV Rd, S0
5855        let vmov = encode_vmov_core_sreg(false, &VfpReg::S0, rd)?;
5856        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
5857
5858        Ok(bytes)
5859    }
5860
5861    /// Encode F64 rounding pseudo-op as Thumb-2 via VCVT to integer and back
5862    /// Encode F64 rounding as Thumb-2.
5863    /// `mode`: FPSCR RMode — 0b00=nearest, 0b01=+inf(ceil), 0b10=-inf(floor), 0b11=zero(trunc)
5864    fn encode_thumb_f64_rounding(&self, dd: &VfpReg, dm: &VfpReg, mode: u8) -> Result<Vec<u8>> {
5865        let mut bytes = Vec::new();
5866        let dm_num = vfp_dreg_to_num(dm)?;
5867        let dd_num = vfp_dreg_to_num(dd)?;
5868        let (vm, m) = encode_dreg(dm_num);
5869        let (vd, d) = encode_dreg(dd_num);
5870
5871        if mode == 0b11 {
5872            // Trunc: VCVTR.S32.F64 — bit[7]=1, always truncates
5873            let vcvt_to_int = 0xEEBD0BC0 | (m << 5) | vm;
5874            bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_int));
5875        } else {
5876            let rt: u32 = 12;
5877
5878            // VMRS R12, FPSCR
5879            let vmrs = 0xEEF10A10 | (rt << 12);
5880            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmrs));
5881
5882            // BIC.W R12, R12, #(3 << 22)
5883            let bic_hw1: u16 = 0xF020 | ((rt as u16) & 0xF);
5884            let bic_hw2: u16 = (0x05 << 12) | ((rt as u16) << 8) | 0x03;
5885            bytes.extend_from_slice(&bic_hw1.to_le_bytes());
5886            bytes.extend_from_slice(&bic_hw2.to_le_bytes());
5887
5888            // ORR.W R12, R12, #(mode << 22)
5889            if mode != 0 {
5890                let orr_hw1: u16 = 0xF040 | ((rt as u16) & 0xF);
5891                let orr_hw2: u16 = (0x05 << 12) | ((rt as u16) << 8) | (mode as u16);
5892                bytes.extend_from_slice(&orr_hw1.to_le_bytes());
5893                bytes.extend_from_slice(&orr_hw2.to_le_bytes());
5894            }
5895
5896            // VMSR FPSCR, R12
5897            let vmsr = 0xEEE10A10 | (rt << 12);
5898            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmsr));
5899
5900            // VCVT.S32.F64 S0, Dm — non-R variant (bit[7]=0)
5901            let vcvt_to_int = 0xEEBD0B40 | (m << 5) | vm;
5902            bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_int));
5903
5904            // Restore FPSCR
5905            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmrs));
5906            bytes.extend_from_slice(&bic_hw1.to_le_bytes());
5907            bytes.extend_from_slice(&bic_hw2.to_le_bytes());
5908            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmsr));
5909        }
5910
5911        // VCVT.F64.S32 Dd, S0
5912        let vcvt_to_float = 0xEEB80B40 | (d << 22) | (vd << 12);
5913        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_float));
5914
5915        Ok(bytes)
5916    }
5917
5918    /// Encode F64 min/max as Thumb-2
5919    fn encode_thumb_f64_minmax(
5920        &self,
5921        dd: &VfpReg,
5922        dn: &VfpReg,
5923        dm: &VfpReg,
5924        is_min: bool,
5925    ) -> Result<Vec<u8>> {
5926        let mut bytes = Vec::new();
5927        let dn_num = vfp_dreg_to_num(dn)?;
5928        let dm_num = vfp_dreg_to_num(dm)?;
5929        let dd_num = vfp_dreg_to_num(dd)?;
5930
5931        // VMOV.F64 Dd, Dn
5932        let (vd, d) = encode_dreg(dd_num);
5933        let (vn, n) = encode_dreg(dn_num);
5934        let vmov_dn = 0xEEB00B40 | (d << 22) | (vd << 12) | (n << 5) | vn;
5935        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov_dn));
5936
5937        // VCMP.F64 Dn, Dm
5938        let (vm, m) = encode_dreg(dm_num);
5939        let vcmp = 0xEEB40B40 | (n << 22) | (vn << 12) | (m << 5) | vm;
5940        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcmp));
5941
5942        // VMRS APSR_nzcv, FPSCR
5943        bytes.extend_from_slice(&vfp_to_thumb_bytes(0xEEF1FA10));
5944
5945        // IT GT (for min) or IT MI (for max)
5946        let cond: u16 = if is_min { 0xC } else { 0x4 };
5947        let it: u16 = 0xBF00 | (cond << 4) | 0x8;
5948        bytes.extend_from_slice(&it.to_le_bytes());
5949
5950        // VMOV{cond}.F64 Dd, Dm
5951        let vmov_dm = 0xEEB00B40 | (d << 22) | (vd << 12) | (m << 5) | vm;
5952        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov_dm));
5953
5954        Ok(bytes)
5955    }
5956
5957    /// Encode F64 copysign as Thumb-2
5958    fn encode_thumb_f64_copysign(&self, dd: &VfpReg, dn: &VfpReg, dm: &VfpReg) -> Result<Vec<u8>> {
5959        let mut bytes = Vec::new();
5960
5961        // VMOV R0, R12, Dm (get sign source)
5962        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_dreg(
5963            false,
5964            dm,
5965            &Reg::R0,
5966            &Reg::R12,
5967        )?));
5968
5969        // VMOV R1, R2, Dn (get magnitude source)
5970        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_dreg(
5971            false,
5972            dn,
5973            &Reg::R1,
5974            &Reg::R2,
5975        )?));
5976
5977        // AND.W R12, R12, #0x80000000 (i=0, Rn=R12)
5978        let hw1: u16 = 0xF000 | 12;
5979        let hw2: u16 = (0x1 << 12) | (12 << 8) | 0x02;
5980        bytes.extend_from_slice(&hw1.to_le_bytes());
5981        bytes.extend_from_slice(&hw2.to_le_bytes());
5982
5983        // BIC.W R2, R2, #0x80000000 (i=0, Rn=R2)
5984        let hw1: u16 = 0xF020 | 2;
5985        let hw2: u16 = (0x1 << 12) | (2 << 8) | 0x02;
5986        bytes.extend_from_slice(&hw1.to_le_bytes());
5987        bytes.extend_from_slice(&hw2.to_le_bytes());
5988
5989        // ORR.W R2, R2, R12
5990        let hw1: u16 = 0xEA40 | 2;
5991        let hw2: u16 = (2 << 8) | 12;
5992        bytes.extend_from_slice(&hw1.to_le_bytes());
5993        bytes.extend_from_slice(&hw2.to_le_bytes());
5994
5995        // VMOV Dd, R1, R2
5996        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_dreg(
5997            true,
5998            dd,
5999            &Reg::R1,
6000            &Reg::R2,
6001        )?));
6002
6003        Ok(bytes)
6004    }
6005
6006    /// Encode VCVT.S32/U32.F32 + VMOV as Thumb-2
6007    fn encode_thumb_i32_trunc_f32(&self, rd: &Reg, sm: &VfpReg, signed: bool) -> Result<Vec<u8>> {
6008        let mut bytes = Vec::new();
6009
6010        let sm_num = vfp_sreg_to_num(sm)?;
6011        let (vd, d) = encode_sreg(sm_num);
6012        let (vm, m) = encode_sreg(sm_num);
6013        let base = if signed { 0xEEBD0AC0 } else { 0xEEBC0AC0 };
6014        let vcvt = base | (d << 22) | (vd << 12) | (m << 5) | vm;
6015        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt));
6016
6017        // VMOV Rd, Sm
6018        let vmov = encode_vmov_core_sreg(false, sm, rd)?;
6019        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
6020
6021        Ok(bytes)
6022    }
6023
6024    // === Thumb-2 32-bit encoding helpers ===
6025
6026    /// Encode Thumb-2 32-bit ADD with immediate
6027    fn encode_thumb32_add(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6028        let rd_bits = reg_to_bits(rd);
6029        let rn_bits = reg_to_bits(rn);
6030
6031        // The `i:imm3:imm8` field is split the same way for both forms.
6032        let i_bit = (imm >> 11) & 1;
6033        let imm3 = (imm >> 8) & 0x7;
6034        let imm8 = imm & 0xFF;
6035
6036        let hw1_base = if imm <= 0xFF {
6037            // ADD.W (T3): the field is a ThumbExpandImm modified immediate. For
6038            // imm <= 0xFF (i:imm3 = 0000) it is the zero-extended byte, which is
6039            // correct — keep this form so existing encodings stay bit-identical.
6040            0xF100
6041        } else if imm <= 0xFFF {
6042            // ADDW (T4): a PLAIN 12-bit immediate (0..4095) — no ThumbExpandImm.
6043            // This is what makes `add sp, sp, #frame` correct for frame sizes
6044            // >= 256, which ADD.W (T3) would silently mis-encode (e.g. #256 -> #0).
6045            0xF200
6046        } else {
6047            return Err(synth_core::Error::synthesis(
6048                "ADD immediate > 0xFFF (4095) requires a multi-instruction sequence (not supported)",
6049            ));
6050        };
6051
6052        let hw1: u16 = (hw1_base | (i_bit << 10) | rn_bits) as u16;
6053        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6054
6055        let mut bytes = hw1.to_le_bytes().to_vec();
6056        bytes.extend_from_slice(&hw2.to_le_bytes());
6057        Ok(bytes)
6058    }
6059
6060    /// Encode Thumb-2 32-bit SUB with immediate
6061    fn encode_thumb32_sub(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6062        let rd_bits = reg_to_bits(rd);
6063        let rn_bits = reg_to_bits(rn);
6064
6065        let i_bit = (imm >> 11) & 1;
6066        let imm3 = (imm >> 8) & 0x7;
6067        let imm8 = imm & 0xFF;
6068
6069        let hw1_base = if imm <= 0xFF {
6070            // SUB.W (T3) modified immediate — correct for the zero-extended byte
6071            // (imm <= 0xFF). Kept bit-identical for existing encodings.
6072            0xF1A0
6073        } else if imm <= 0xFFF {
6074            // SUBW (T4): plain 12-bit immediate (0..4095). Makes
6075            // `sub sp, sp, #frame` correct for frame sizes >= 256.
6076            0xF2A0
6077        } else {
6078            return Err(synth_core::Error::synthesis(
6079                "SUB immediate > 0xFFF (4095) requires a multi-instruction sequence (not supported)",
6080            ));
6081        };
6082
6083        let hw1: u16 = (hw1_base | (i_bit << 10) | rn_bits) as u16;
6084        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6085
6086        let mut bytes = hw1.to_le_bytes().to_vec();
6087        bytes.extend_from_slice(&hw2.to_le_bytes());
6088        Ok(bytes)
6089    }
6090
6091    /// Encode Thumb-2 32-bit ADDS with immediate (sets flags)
6092    fn encode_thumb32_adds(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6093        let rd_bits = reg_to_bits(rd);
6094        let rn_bits = reg_to_bits(rn);
6095
6096        // ADDS.W (flag-setting) has only the modified-immediate form — error on
6097        // an un-encodable value rather than silently add the wrong constant.
6098        let field = try_thumb_expand_imm(imm).ok_or_else(|| {
6099            synth_core::Error::synthesis(
6100                "ADDS immediate is not a valid ThumbExpandImm — materialize into a register",
6101            )
6102        })?;
6103        let i_bit = (field >> 11) & 1;
6104        let imm3 = (field >> 8) & 0x7;
6105        let imm8 = field & 0xFF;
6106
6107        // ADDS.W Rd, Rn, #imm (with S=1)
6108        // First halfword: 1111 0 i 0 1000 1 Rn = F110 | i<<10 | Rn
6109        let hw1: u16 = (0xF110 | (i_bit << 10) | rn_bits) as u16;
6110        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6111
6112        let mut bytes = hw1.to_le_bytes().to_vec();
6113        bytes.extend_from_slice(&hw2.to_le_bytes());
6114        Ok(bytes)
6115    }
6116
6117    /// Encode Thumb-2 32-bit SUBS with immediate (sets flags)
6118    fn encode_thumb32_subs(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6119        let rd_bits = reg_to_bits(rd);
6120        let rn_bits = reg_to_bits(rn);
6121
6122        // SUBS.W (flag-setting) has only the modified-immediate form — error on
6123        // an un-encodable value rather than silently subtract the wrong constant.
6124        let field = try_thumb_expand_imm(imm).ok_or_else(|| {
6125            synth_core::Error::synthesis(
6126                "SUBS immediate is not a valid ThumbExpandImm — materialize into a register",
6127            )
6128        })?;
6129        let i_bit = (field >> 11) & 1;
6130        let imm3 = (field >> 8) & 0x7;
6131        let imm8 = field & 0xFF;
6132
6133        // SUBS.W Rd, Rn, #imm (with S=1)
6134        // First halfword: 1111 0 i 0 1101 1 Rn = F1B0 | i<<10 | Rn
6135        let hw1: u16 = (0xF1B0 | (i_bit << 10) | rn_bits) as u16;
6136        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6137
6138        let mut bytes = hw1.to_le_bytes().to_vec();
6139        bytes.extend_from_slice(&hw2.to_le_bytes());
6140        Ok(bytes)
6141    }
6142
6143    /// Encode Thumb-2 32-bit MOVW (16-bit immediate)
6144    ///
6145    /// # Contract (Verus-style)
6146    /// ```text
6147    /// requires rd <= R14
6148    /// ensures result.len() == 4
6149    /// ensures (imm & 0xFFFF) can be reconstructed from the encoding
6150    /// ```
6151    fn encode_thumb32_movw(&self, rd: &Reg, imm: u32) -> Result<Vec<u8>> {
6152        let rd_bits = reg_to_bits(rd);
6153        reg_bits_checked(rd_bits)?;
6154        let imm16 = imm & 0xFFFF;
6155
6156        // MOVW Rd, #imm16
6157        // 1111 0 i 10 0 1 0 0 imm4 | 0 imm3 Rd imm8
6158        let imm4 = (imm16 >> 12) & 0xF;
6159        let i_bit = (imm16 >> 11) & 1;
6160        let imm3 = (imm16 >> 8) & 0x7;
6161        let imm8 = imm16 & 0xFF;
6162
6163        let hw1: u16 = (0xF240 | (i_bit << 10) | imm4) as u16;
6164        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6165
6166        let mut bytes = hw1.to_le_bytes().to_vec();
6167        bytes.extend_from_slice(&hw2.to_le_bytes());
6168        encoding_contracts::verify_thumb32(&bytes);
6169        Ok(bytes)
6170    }
6171
6172    /// Encode Thumb-2 32-bit shift with immediate
6173    ///
6174    /// # Contract (Verus-style)
6175    /// ```text
6176    /// requires rd <= R14, rm <= R14
6177    /// ensures result.len() == 4
6178    /// ```
6179    fn encode_thumb32_shift(
6180        &self,
6181        rd: &Reg,
6182        rm: &Reg,
6183        shift: u32,
6184        shift_type: u8,
6185    ) -> Result<Vec<u8>> {
6186        let rd_bits = reg_to_bits(rd);
6187        let rm_bits = reg_to_bits(rm);
6188        reg_bits_checked(rd_bits)?;
6189        reg_bits_checked(rm_bits)?;
6190        let imm5 = shift & 0x1F;
6191        let imm2 = imm5 & 0x3;
6192        let imm3 = (imm5 >> 2) & 0x7;
6193
6194        // MOV.W Rd, Rm, <shift> #imm
6195        // EA4F 0 imm3 Rd imm2 type Rm
6196        let hw1: u16 = 0xEA4F;
6197        let hw2: u16 =
6198            ((imm3 << 12) | (rd_bits << 8) | (imm2 << 6) | ((shift_type as u32) << 4) | rm_bits)
6199                as u16;
6200
6201        let mut bytes = hw1.to_le_bytes().to_vec();
6202        bytes.extend_from_slice(&hw2.to_le_bytes());
6203        Ok(bytes)
6204    }
6205
6206    /// Encode Thumb-2 32-bit shift by register
6207    /// Encoding: 11111010 0xx0 Rn | 1111 Rd 0000 Rm
6208    /// shift_type: 00=LSL, 01=LSR, 10=ASR, 11=ROR
6209    fn encode_thumb32_shift_reg(
6210        &self,
6211        rd: &Reg,
6212        rn: &Reg,
6213        rm: &Reg,
6214        shift_type: u8,
6215    ) -> Result<Vec<u8>> {
6216        let rd_bits = reg_to_bits(rd);
6217        let rn_bits = reg_to_bits(rn);
6218        let rm_bits = reg_to_bits(rm);
6219
6220        // hw1: 1111 1010 0xx0 Rn
6221        let hw1: u16 = (0xFA00 | ((shift_type as u32) << 5) | rn_bits) as u16;
6222        // hw2: 1111 Rd 0000 Rm
6223        let hw2: u16 = (0xF000 | (rd_bits << 8) | rm_bits) as u16;
6224
6225        let mut bytes = hw1.to_le_bytes().to_vec();
6226        bytes.extend_from_slice(&hw2.to_le_bytes());
6227        Ok(bytes)
6228    }
6229
6230    /// Encode Thumb-2 32-bit CMP with immediate
6231    fn encode_thumb32_cmp_imm(&self, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6232        let rn_bits = reg_to_bits(rn);
6233
6234        // CMP.W has only the modified-immediate form (no plain-imm12 like ADDW),
6235        // so an un-encodable immediate MUST be materialized into a register by
6236        // the selector. Error rather than silently compare the wrong constant.
6237        let field = try_thumb_expand_imm(imm).ok_or_else(|| {
6238            synth_core::Error::synthesis(
6239                "CMP immediate is not a valid ThumbExpandImm — materialize into a register",
6240            )
6241        })?;
6242        let i_bit = (field >> 11) & 1;
6243        let imm3 = (field >> 8) & 0x7;
6244        let imm8 = field & 0xFF;
6245
6246        // CMP.W Rn, #imm
6247        let hw1: u16 = (0xF1B0 | (i_bit << 10) | rn_bits) as u16;
6248        let hw2: u16 = ((imm3 << 12) | 0x0F00 | imm8) as u16;
6249
6250        let mut bytes = hw1.to_le_bytes().to_vec();
6251        bytes.extend_from_slice(&hw2.to_le_bytes());
6252        Ok(bytes)
6253    }
6254
6255    /// Encode Thumb-2 32-bit LDR
6256    fn encode_thumb32_ldr(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6257        let rd_bits = reg_to_bits(rd);
6258        let base_bits = reg_to_bits(base);
6259
6260        // LDR.W Rd, [Rn, #imm12]
6261        let hw1: u16 = (0xF8D0 | base_bits) as u16;
6262        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6263
6264        let mut bytes = hw1.to_le_bytes().to_vec();
6265        bytes.extend_from_slice(&hw2.to_le_bytes());
6266        Ok(bytes)
6267    }
6268
6269    /// Encode Thumb-2 32-bit STR
6270    fn encode_thumb32_str(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6271        let rd_bits = reg_to_bits(rd);
6272        let base_bits = reg_to_bits(base);
6273
6274        // STR.W Rd, [Rn, #imm12]
6275        let hw1: u16 = (0xF8C0 | base_bits) as u16;
6276        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) 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 LDR with register offset: LDR.W Rd, [Rn, Rm]
6284    fn encode_thumb32_ldr_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6285        let rd_bits = reg_to_bits(rd);
6286        let base_bits = reg_to_bits(base);
6287        let rm_bits = reg_to_bits(offset_reg);
6288
6289        // LDR.W Rd, [Rn, Rm, LSL #0]
6290        // Encoding: 1111 1000 0101 Rn | Rt 0000 00 imm2 Rm
6291        // imm2 = 00 for no shift (LSL #0)
6292        let hw1: u16 = (0xF850 | base_bits) as u16;
6293        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6294
6295        let mut bytes = hw1.to_le_bytes().to_vec();
6296        bytes.extend_from_slice(&hw2.to_le_bytes());
6297        Ok(bytes)
6298    }
6299
6300    /// Encode Thumb-2 32-bit STR with register offset: STR.W Rd, [Rn, Rm]
6301    fn encode_thumb32_str_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6302        let rd_bits = reg_to_bits(rd);
6303        let base_bits = reg_to_bits(base);
6304        let rm_bits = reg_to_bits(offset_reg);
6305
6306        // STR.W Rd, [Rn, Rm, LSL #0]
6307        // Encoding: 1111 1000 0100 Rn | Rt 0000 00 imm2 Rm
6308        // imm2 = 00 for no shift (LSL #0)
6309        let hw1: u16 = (0xF840 | base_bits) as u16;
6310        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6311
6312        let mut bytes = hw1.to_le_bytes().to_vec();
6313        bytes.extend_from_slice(&hw2.to_le_bytes());
6314        Ok(bytes)
6315    }
6316
6317    // === Sub-word load/store Thumb-2 encoding helpers ===
6318
6319    /// Encode Thumb-2 32-bit LDRB with immediate: LDRB.W Rd, [Rn, #imm12]
6320    fn encode_thumb32_ldrb_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6321        let rd_bits = reg_to_bits(rd);
6322        let base_bits = reg_to_bits(base);
6323        // LDRB.W Rd, [Rn, #imm12]: 1111 1000 1001 Rn | Rt imm12
6324        let hw1: u16 = (0xF890 | base_bits) as u16;
6325        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6326        let mut bytes = hw1.to_le_bytes().to_vec();
6327        bytes.extend_from_slice(&hw2.to_le_bytes());
6328        Ok(bytes)
6329    }
6330
6331    /// Encode Thumb-2 32-bit LDRB with register: LDRB.W Rd, [Rn, Rm]
6332    fn encode_thumb32_ldrb_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6333        let rd_bits = reg_to_bits(rd);
6334        let base_bits = reg_to_bits(base);
6335        let rm_bits = reg_to_bits(offset_reg);
6336        // LDRB.W Rd, [Rn, Rm, LSL #0]: 1111 1000 0001 Rn | Rt 0000 00 imm2 Rm
6337        let hw1: u16 = (0xF810 | base_bits) as u16;
6338        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6339        let mut bytes = hw1.to_le_bytes().to_vec();
6340        bytes.extend_from_slice(&hw2.to_le_bytes());
6341        Ok(bytes)
6342    }
6343
6344    /// Encode Thumb-2 32-bit LDRSB with immediate: LDRSB.W Rd, [Rn, #imm12]
6345    fn encode_thumb32_ldrsb_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6346        let rd_bits = reg_to_bits(rd);
6347        let base_bits = reg_to_bits(base);
6348        // LDRSB.W Rd, [Rn, #imm12]: 1111 1001 1001 Rn | Rt imm12
6349        let hw1: u16 = (0xF990 | base_bits) as u16;
6350        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6351        let mut bytes = hw1.to_le_bytes().to_vec();
6352        bytes.extend_from_slice(&hw2.to_le_bytes());
6353        Ok(bytes)
6354    }
6355
6356    /// Encode Thumb-2 32-bit LDRSB with register: LDRSB.W Rd, [Rn, Rm]
6357    fn encode_thumb32_ldrsb_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6358        let rd_bits = reg_to_bits(rd);
6359        let base_bits = reg_to_bits(base);
6360        let rm_bits = reg_to_bits(offset_reg);
6361        // LDRSB.W Rd, [Rn, Rm, LSL #0]: 1111 1001 0001 Rn | Rt 0000 00 imm2 Rm
6362        let hw1: u16 = (0xF910 | base_bits) as u16;
6363        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6364        let mut bytes = hw1.to_le_bytes().to_vec();
6365        bytes.extend_from_slice(&hw2.to_le_bytes());
6366        Ok(bytes)
6367    }
6368
6369    /// Encode Thumb-2 32-bit LDRH with immediate: LDRH.W Rd, [Rn, #imm12]
6370    fn encode_thumb32_ldrh_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6371        let rd_bits = reg_to_bits(rd);
6372        let base_bits = reg_to_bits(base);
6373        // LDRH.W Rd, [Rn, #imm12]: 1111 1000 1011 Rn | Rt imm12
6374        let hw1: u16 = (0xF8B0 | base_bits) as u16;
6375        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6376        let mut bytes = hw1.to_le_bytes().to_vec();
6377        bytes.extend_from_slice(&hw2.to_le_bytes());
6378        Ok(bytes)
6379    }
6380
6381    /// Encode Thumb-2 32-bit LDRH with register: LDRH.W Rd, [Rn, Rm]
6382    fn encode_thumb32_ldrh_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6383        let rd_bits = reg_to_bits(rd);
6384        let base_bits = reg_to_bits(base);
6385        let rm_bits = reg_to_bits(offset_reg);
6386        // LDRH.W Rd, [Rn, Rm, LSL #0]: 1111 1000 0011 Rn | Rt 0000 00 imm2 Rm
6387        let hw1: u16 = (0xF830 | base_bits) as u16;
6388        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6389        let mut bytes = hw1.to_le_bytes().to_vec();
6390        bytes.extend_from_slice(&hw2.to_le_bytes());
6391        Ok(bytes)
6392    }
6393
6394    /// Encode Thumb-2 32-bit LDRSH with immediate: LDRSH.W Rd, [Rn, #imm12]
6395    fn encode_thumb32_ldrsh_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6396        let rd_bits = reg_to_bits(rd);
6397        let base_bits = reg_to_bits(base);
6398        // LDRSH.W Rd, [Rn, #imm12]: 1111 1001 1011 Rn | Rt imm12
6399        let hw1: u16 = (0xF9B0 | base_bits) as u16;
6400        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6401        let mut bytes = hw1.to_le_bytes().to_vec();
6402        bytes.extend_from_slice(&hw2.to_le_bytes());
6403        Ok(bytes)
6404    }
6405
6406    /// Encode Thumb-2 32-bit LDRSH with register: LDRSH.W Rd, [Rn, Rm]
6407    fn encode_thumb32_ldrsh_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6408        let rd_bits = reg_to_bits(rd);
6409        let base_bits = reg_to_bits(base);
6410        let rm_bits = reg_to_bits(offset_reg);
6411        // LDRSH.W Rd, [Rn, Rm, LSL #0]: 1111 1001 0011 Rn | Rt 0000 00 imm2 Rm
6412        let hw1: u16 = (0xF930 | base_bits) as u16;
6413        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6414        let mut bytes = hw1.to_le_bytes().to_vec();
6415        bytes.extend_from_slice(&hw2.to_le_bytes());
6416        Ok(bytes)
6417    }
6418
6419    /// Encode Thumb-2 32-bit STRB with immediate: STRB.W Rd, [Rn, #imm12]
6420    fn encode_thumb32_strb_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6421        let rd_bits = reg_to_bits(rd);
6422        let base_bits = reg_to_bits(base);
6423        // STRB.W Rd, [Rn, #imm12]: 1111 1000 1000 Rn | Rt imm12
6424        let hw1: u16 = (0xF880 | base_bits) as u16;
6425        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6426        let mut bytes = hw1.to_le_bytes().to_vec();
6427        bytes.extend_from_slice(&hw2.to_le_bytes());
6428        Ok(bytes)
6429    }
6430
6431    /// Encode Thumb-2 32-bit STRB with register: STRB.W Rd, [Rn, Rm]
6432    fn encode_thumb32_strb_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6433        let rd_bits = reg_to_bits(rd);
6434        let base_bits = reg_to_bits(base);
6435        let rm_bits = reg_to_bits(offset_reg);
6436        // STRB.W Rd, [Rn, Rm, LSL #0]: 1111 1000 0000 Rn | Rt 0000 00 imm2 Rm
6437        let hw1: u16 = (0xF800 | base_bits) as u16;
6438        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6439        let mut bytes = hw1.to_le_bytes().to_vec();
6440        bytes.extend_from_slice(&hw2.to_le_bytes());
6441        Ok(bytes)
6442    }
6443
6444    /// Encode Thumb-2 32-bit STRH with immediate: STRH.W Rd, [Rn, #imm12]
6445    fn encode_thumb32_strh_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6446        let rd_bits = reg_to_bits(rd);
6447        let base_bits = reg_to_bits(base);
6448        // STRH.W Rd, [Rn, #imm12]: 1111 1000 1010 Rn | Rt imm12
6449        let hw1: u16 = (0xF8A0 | base_bits) as u16;
6450        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6451        let mut bytes = hw1.to_le_bytes().to_vec();
6452        bytes.extend_from_slice(&hw2.to_le_bytes());
6453        Ok(bytes)
6454    }
6455
6456    /// Encode Thumb-2 32-bit STRH with register: STRH.W Rd, [Rn, Rm]
6457    fn encode_thumb32_strh_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6458        let rd_bits = reg_to_bits(rd);
6459        let base_bits = reg_to_bits(base);
6460        let rm_bits = reg_to_bits(offset_reg);
6461        // STRH.W Rd, [Rn, Rm, LSL #0]: 1111 1000 0010 Rn | Rt 0000 00 imm2 Rm
6462        let hw1: u16 = (0xF820 | base_bits) as u16;
6463        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6464        let mut bytes = hw1.to_le_bytes().to_vec();
6465        bytes.extend_from_slice(&hw2.to_le_bytes());
6466        Ok(bytes)
6467    }
6468
6469    /// Encode Thumb-2 32-bit ADD with immediate: ADD.W Rd, Rn, #imm
6470    fn encode_thumb32_add_imm(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6471        let rd_bits = reg_to_bits(rd);
6472        let rn_bits = reg_to_bits(rn);
6473
6474        // For small immediates, use ADD.W Rd, Rn, #imm12
6475        // Encoding: 1111 0 i 0 1 0 0 0 S Rn | 0 imm3 Rd imm8
6476        // S = 0 (don't update flags)
6477        // The 12-bit immediate is encoded as: i:imm3:imm8
6478        // For simplicity, we only support imm <= 0xFFF (direct encoding)
6479        if imm <= 0xFFF {
6480            let i_bit = (imm >> 11) & 1;
6481            let imm3 = (imm >> 8) & 0x7;
6482            let imm8 = imm & 0xFF;
6483
6484            let hw1: u16 = (0xF100 | (i_bit << 10) | rn_bits) as u16;
6485            let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6486
6487            let mut bytes = hw1.to_le_bytes().to_vec();
6488            bytes.extend_from_slice(&hw2.to_le_bytes());
6489            Ok(bytes)
6490        } else {
6491            // For larger immediates, would need MOVW/MOVT + ADD
6492            // For now, return error
6493            Err(synth_core::Error::synthesis(
6494                "ADD immediate too large for single instruction",
6495            ))
6496        }
6497    }
6498
6499    // === Raw encoding helpers for POPCNT (take register numbers directly) ===
6500
6501    /// Encode Thumb-2 32-bit MOVW (16-bit immediate) - raw version
6502    ///
6503    /// # Contract (Verus-style)
6504    /// ```text
6505    /// requires rd <= 14, imm16 <= 0xFFFF
6506    /// ensures result.len() == 4
6507    /// ```
6508    fn encode_thumb32_movw_raw(&self, rd: u32, imm16: u32) -> Result<Vec<u8>> {
6509        reg_bits_checked(rd)?;
6510        encoding_contracts::verify_imm16(imm16);
6511        // MOVW Rd, #imm16
6512        // 1111 0 i 10 0 1 0 0 imm4 | 0 imm3 Rd imm8
6513        let imm16 = imm16 & 0xFFFF;
6514        let imm4 = (imm16 >> 12) & 0xF;
6515        let i_bit = (imm16 >> 11) & 1;
6516        let imm3 = (imm16 >> 8) & 0x7;
6517        let imm8 = imm16 & 0xFF;
6518
6519        let hw1: u16 = (0xF240 | (i_bit << 10) | imm4) as u16;
6520        let hw2: u16 = ((imm3 << 12) | (rd << 8) | imm8) as u16;
6521
6522        let mut bytes = hw1.to_le_bytes().to_vec();
6523        bytes.extend_from_slice(&hw2.to_le_bytes());
6524        encoding_contracts::verify_thumb32(&bytes);
6525        Ok(bytes)
6526    }
6527
6528    /// Encode Thumb-2 32-bit MOVT (move top 16 bits) - raw version
6529    ///
6530    /// # Contract (Verus-style)
6531    /// ```text
6532    /// requires rd <= 14, imm16 <= 0xFFFF
6533    /// ensures result.len() == 4
6534    /// ```
6535    fn encode_thumb32_movt_raw(&self, rd: u32, imm16: u32) -> Result<Vec<u8>> {
6536        reg_bits_checked(rd)?;
6537        encoding_contracts::verify_imm16(imm16);
6538        // MOVT Rd, #imm16
6539        // 1111 0 i 10 1 1 0 0 imm4 | 0 imm3 Rd imm8
6540        let imm16 = imm16 & 0xFFFF;
6541        let imm4 = (imm16 >> 12) & 0xF;
6542        let i_bit = (imm16 >> 11) & 1;
6543        let imm3 = (imm16 >> 8) & 0x7;
6544        let imm8 = imm16 & 0xFF;
6545
6546        let hw1: u16 = (0xF2C0 | (i_bit << 10) | imm4) as u16;
6547        let hw2: u16 = ((imm3 << 12) | (rd << 8) | imm8) as u16;
6548
6549        let mut bytes = hw1.to_le_bytes().to_vec();
6550        bytes.extend_from_slice(&hw2.to_le_bytes());
6551        encoding_contracts::verify_thumb32(&bytes);
6552        Ok(bytes)
6553    }
6554
6555    /// Encode Thumb-2 32-bit LSR (logical shift right) with immediate - raw version
6556    fn encode_thumb32_lsr_raw(&self, rd: u32, rm: u32, shift: u32) -> Result<Vec<u8>> {
6557        // MOV.W Rd, Rm, LSR #imm
6558        // EA4F 0 imm3 Rd imm2 01 Rm
6559        let imm5 = shift & 0x1F;
6560        let imm2 = imm5 & 0x3;
6561        let imm3 = (imm5 >> 2) & 0x7;
6562
6563        let hw1: u16 = 0xEA4F;
6564        let hw2: u16 = ((imm3 << 12) | (rd << 8) | (imm2 << 6) | (0b01 << 4) | rm) as u16;
6565
6566        let mut bytes = hw1.to_le_bytes().to_vec();
6567        bytes.extend_from_slice(&hw2.to_le_bytes());
6568        Ok(bytes)
6569    }
6570
6571    /// Encode Thumb-2 32-bit AND (register) - raw version
6572    fn encode_thumb32_and_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6573        // AND.W Rd, Rn, Rm
6574        // EA00 Rn | 0 Rd 00 00 Rm
6575        let hw1: u16 = (0xEA00 | rn) as u16;
6576        let hw2: u16 = ((rd << 8) | rm) as u16;
6577
6578        let mut bytes = hw1.to_le_bytes().to_vec();
6579        bytes.extend_from_slice(&hw2.to_le_bytes());
6580        Ok(bytes)
6581    }
6582
6583    /// Encode Thumb-2 32-bit AND with immediate - raw version
6584    fn encode_thumb32_and_imm_raw(&self, rd: u32, rn: u32, imm: u32) -> Result<Vec<u8>> {
6585        // AND.W Rd, Rn, #<modified_immediate>
6586        // For small immediates (0-255), the encoding is simpler
6587        // F0 00 Rn | 0 imm3 Rd imm8
6588        let i_bit = (imm >> 11) & 1;
6589        let imm3 = (imm >> 8) & 0x7;
6590        let imm8 = imm & 0xFF;
6591
6592        let hw1: u16 = (0xF000 | (i_bit << 10) | rn) as u16;
6593        let hw2: u16 = ((imm3 << 12) | (rd << 8) | imm8) as u16;
6594
6595        let mut bytes = hw1.to_le_bytes().to_vec();
6596        bytes.extend_from_slice(&hw2.to_le_bytes());
6597        Ok(bytes)
6598    }
6599
6600    /// Encode Thumb-2 32-bit SUB (register) - raw version
6601    fn encode_thumb32_sub_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6602        // SUB.W Rd, Rn, Rm
6603        // EBA0 Rn | 0 Rd 00 00 Rm
6604        let hw1: u16 = (0xEBA0 | rn) as u16;
6605        let hw2: u16 = ((rd << 8) | rm) as u16;
6606
6607        let mut bytes = hw1.to_le_bytes().to_vec();
6608        bytes.extend_from_slice(&hw2.to_le_bytes());
6609        Ok(bytes)
6610    }
6611
6612    /// Encode Thumb-2 32-bit ADD (register) - raw version
6613    fn encode_thumb32_add_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6614        // ADD.W Rd, Rn, Rm
6615        // EB00 Rn | 0 Rd 00 00 Rm
6616        let hw1: u16 = (0xEB00 | rn) as u16;
6617        let hw2: u16 = ((rd << 8) | rm) as u16;
6618
6619        let mut bytes = hw1.to_le_bytes().to_vec();
6620        bytes.extend_from_slice(&hw2.to_le_bytes());
6621        Ok(bytes)
6622    }
6623
6624    /// Encode Thumb-2 32-bit ADDS (register, flag-setting) - raw version.
6625    /// Used as the high-register fallback for `ArmOp::Adds` (i64 low-word add)
6626    /// so R8-R11 pair operands don't overflow the 16-bit field — #178/#180.
6627    fn encode_thumb32_adds_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6628        // ADDS.W Rd, Rn, Rm (T3, S=1): EB10 Rn | 0 Rd 00 00 Rm
6629        let hw1: u16 = (0xEB10 | rn) as u16;
6630        let hw2: u16 = ((rd << 8) | rm) as u16;
6631        let mut bytes = hw1.to_le_bytes().to_vec();
6632        bytes.extend_from_slice(&hw2.to_le_bytes());
6633        Ok(bytes)
6634    }
6635
6636    /// Encode Thumb-2 32-bit SUBS (register, flag-setting) - raw version.
6637    /// High-register fallback for `ArmOp::Subs` (i64 low-word subtract) — #178/#180.
6638    fn encode_thumb32_subs_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6639        // SUBS.W Rd, Rn, Rm (T3, S=1): EBB0 Rn | 0 Rd 00 00 Rm
6640        let hw1: u16 = (0xEBB0 | rn) as u16;
6641        let hw2: u16 = ((rd << 8) | rm) as u16;
6642        let mut bytes = hw1.to_le_bytes().to_vec();
6643        bytes.extend_from_slice(&hw2.to_le_bytes());
6644        Ok(bytes)
6645    }
6646
6647    /// Encode a sequence of ARM instructions
6648    pub fn encode_sequence(&self, ops: &[ArmOp]) -> Result<Vec<u8>> {
6649        let mut code = Vec::new();
6650
6651        for op in ops {
6652            let encoded = self.encode(op)?;
6653            code.extend_from_slice(&encoded);
6654        }
6655
6656        Ok(code)
6657    }
6658}
6659
6660/// Convert register to bit encoding (0-15)
6661/// Reverse of the ARMv7-M `ThumbExpandImm`: given a 32-bit immediate, return the
6662/// 12-bit `i:imm3:imm8` field if it is a representable modified immediate, else
6663/// `None` (the caller must materialize the value into a register). This is the
6664/// shared correct path for the data-processing immediate encoders — without it
6665/// they pack raw bits and silently mis-encode any value `> 0xFF` that isn't a
6666/// modified immediate (the silent-miscompile class behind #251/#253/#255).
6667fn try_thumb_expand_imm(value: u32) -> Option<u32> {
6668    // i:imm3 = 0000 → 8-bit value, zero-extended (00000000 00000000 00000000 XY).
6669    if value <= 0xFF {
6670        return Some(value);
6671    }
6672    let b0 = value & 0xFF; // byte 0
6673    let b1 = (value >> 8) & 0xFF; // byte 1
6674    // 0x00XY00XY (i:imm3 = 0001) — XY in bytes 0 and 2
6675    if value == (b0 << 16) | b0 {
6676        return Some(0x100 | b0);
6677    }
6678    // 0xXY00XY00 (i:imm3 = 0010) — XY in bytes 1 and 3
6679    if value == (b1 << 24) | (b1 << 8) {
6680        return Some(0x200 | b1);
6681    }
6682    // 0xXYXYXYXY (i:imm3 = 0011) — XY in all four bytes
6683    if value == (b0 << 24) | (b0 << 16) | (b0 << 8) | b0 {
6684        return Some(0x300 | b0);
6685    }
6686    // An 8-bit value with bit 7 set, rotated right by 8..=31. `rotate_left(rot)`
6687    // undoes the encoded right rotation; if the result is `1bbbbbbb` (0x80..=0xFF)
6688    // the value is representable. imm12[11:7] = rot, imm12[6:0] = low 7 bits.
6689    for rot in 8..=31u32 {
6690        let unrot = value.rotate_left(rot);
6691        if (0x80..=0xFF).contains(&unrot) {
6692            return Some((rot << 7) | (unrot & 0x7F));
6693        }
6694    }
6695    None
6696}
6697
6698fn reg_to_bits(reg: &Reg) -> u32 {
6699    match reg {
6700        Reg::R0 => 0,
6701        Reg::R1 => 1,
6702        Reg::R2 => 2,
6703        Reg::R3 => 3,
6704        Reg::R4 => 4,
6705        Reg::R5 => 5,
6706        Reg::R6 => 6,
6707        Reg::R7 => 7,
6708        Reg::R8 => 8,
6709        Reg::R9 => 9,
6710        Reg::R10 => 10,
6711        Reg::R11 => 11,
6712        Reg::R12 => 12,
6713        Reg::SP => 13,
6714        Reg::LR => 14,
6715        Reg::PC => 15,
6716    }
6717}
6718
6719/// Fallible form of the `verify_reg_bits` contract. PC (R15) is not a valid
6720/// data operand for the Thumb-2 encodings that use this guard (SDIV/UDIV/MLS/…
6721/// are UNPREDICTABLE with PC). Synth's own codegen never emits PC there, but
6722/// the encoder must stay *total* over arbitrary `ArmOp` inputs — the fuzz
6723/// harness (`encoder_no_panic`) requires Ok-or-Err, never a panic. Pre-fix, the
6724/// `debug_assert` in `verify_reg_bits` aborted under `-Cdebug-assertions`.
6725/// Returns a typed Err instead. See #185.
6726fn reg_bits_checked(bits: u32) -> Result<()> {
6727    if bits > 14 {
6728        return Err(synth_core::Error::synthesis(format!(
6729            "register bits {bits} (PC/R15) is not a valid operand for this Thumb-2 encoding"
6730        )));
6731    }
6732    Ok(())
6733}
6734
6735/// Try to encode a 32-bit value as an ARM rotated immediate (imm8 ROR 2*rot4).
6736/// Returns Some((encoded_bits, 1)) if representable, None otherwise.
6737fn try_encode_rotated_imm(val: u32) -> Option<(u32, u32)> {
6738    if val == 0 {
6739        return Some((0, 1));
6740    }
6741    for rot in 0..16u32 {
6742        let shift = rot * 2;
6743        // Rotate left by shift (undo the ROR) to see if result fits in 8 bits
6744        let unrotated = val.rotate_left(shift);
6745        if unrotated <= 0xFF {
6746            // Encoded as: rot4(4 bits) | imm8(8 bits) = rotate_imm << 8 | imm8
6747            return Some(((rot << 8) | unrotated, 1));
6748        }
6749    }
6750    None
6751}
6752
6753/// Encode operand2 field and return (bits, immediate_flag).
6754/// For ARM32 mode, immediates use the rotated-immediate encoding (imm8 ROR 2*rot4).
6755/// Panics if an immediate value cannot be represented. Callers that need large
6756/// immediates should use MOVW/MOVT instead of Operand2::Imm.
6757fn encode_operand2(op2: &Operand2) -> (u32, u32) {
6758    match op2 {
6759        Operand2::Imm(val) => {
6760            let uval = *val as u32;
6761            // Attempt rotated-immediate encoding (ARM32 Operand2)
6762            if let Some(encoded) = try_encode_rotated_imm(uval) {
6763                encoded
6764            } else {
6765                // Fallback: mask to 8 bits (legacy behavior for values that
6766                // cannot be represented). This should not be reached for
6767                // correctly-selected instructions; the instruction selector
6768                // must use MOVW/MOVT for large constants.
6769                let imm = uval & 0xFF;
6770                (imm, 1)
6771            }
6772        }
6773
6774        Operand2::Reg(reg) => {
6775            let reg_bits = reg_to_bits(reg);
6776            (reg_bits, 0) // I=0 for register
6777        }
6778
6779        Operand2::RegShift {
6780            rm,
6781            shift: _,
6782            amount,
6783        } => {
6784            // Simplified encoding with shift
6785            let rm_bits = reg_to_bits(rm);
6786            let shift_bits = (*amount & 0x1F) << 7;
6787            (shift_bits | rm_bits, 0)
6788        }
6789    }
6790}
6791
6792/// Encode memory address to (base_reg, offset)
6793fn encode_mem_addr(addr: &MemAddr) -> (u32, u32) {
6794    let base_bits = reg_to_bits(&addr.base);
6795    let offset_bits = (addr.offset as u32) & 0xFFF; // 12-bit offset
6796    (base_bits, offset_bits)
6797}
6798
6799/// S-register number: S0=0, S1=1, ..., S31=31
6800fn vfp_sreg_to_num(reg: &VfpReg) -> Result<u32> {
6801    match reg {
6802        VfpReg::S0 => Ok(0),
6803        VfpReg::S1 => Ok(1),
6804        VfpReg::S2 => Ok(2),
6805        VfpReg::S3 => Ok(3),
6806        VfpReg::S4 => Ok(4),
6807        VfpReg::S5 => Ok(5),
6808        VfpReg::S6 => Ok(6),
6809        VfpReg::S7 => Ok(7),
6810        VfpReg::S8 => Ok(8),
6811        VfpReg::S9 => Ok(9),
6812        VfpReg::S10 => Ok(10),
6813        VfpReg::S11 => Ok(11),
6814        VfpReg::S12 => Ok(12),
6815        VfpReg::S13 => Ok(13),
6816        VfpReg::S14 => Ok(14),
6817        VfpReg::S15 => Ok(15),
6818        VfpReg::S16 => Ok(16),
6819        VfpReg::S17 => Ok(17),
6820        VfpReg::S18 => Ok(18),
6821        VfpReg::S19 => Ok(19),
6822        VfpReg::S20 => Ok(20),
6823        VfpReg::S21 => Ok(21),
6824        VfpReg::S22 => Ok(22),
6825        VfpReg::S23 => Ok(23),
6826        VfpReg::S24 => Ok(24),
6827        VfpReg::S25 => Ok(25),
6828        VfpReg::S26 => Ok(26),
6829        VfpReg::S27 => Ok(27),
6830        VfpReg::S28 => Ok(28),
6831        VfpReg::S29 => Ok(29),
6832        VfpReg::S30 => Ok(30),
6833        VfpReg::S31 => Ok(31),
6834        // D-registers are not used in F32 single-precision encodings
6835        _ => Err(synth_core::Error::SynthesisError(
6836            "D-register not supported in single-precision VFP encoding".to_string(),
6837        )),
6838    }
6839}
6840
6841/// D-register number: D0=0, D1=1, ..., D15=15
6842fn vfp_dreg_to_num(reg: &VfpReg) -> Result<u32> {
6843    match reg {
6844        VfpReg::D0 => Ok(0),
6845        VfpReg::D1 => Ok(1),
6846        VfpReg::D2 => Ok(2),
6847        VfpReg::D3 => Ok(3),
6848        VfpReg::D4 => Ok(4),
6849        VfpReg::D5 => Ok(5),
6850        VfpReg::D6 => Ok(6),
6851        VfpReg::D7 => Ok(7),
6852        VfpReg::D8 => Ok(8),
6853        VfpReg::D9 => Ok(9),
6854        VfpReg::D10 => Ok(10),
6855        VfpReg::D11 => Ok(11),
6856        VfpReg::D12 => Ok(12),
6857        VfpReg::D13 => Ok(13),
6858        VfpReg::D14 => Ok(14),
6859        VfpReg::D15 => Ok(15),
6860        // S-registers are not used in F64 double-precision encodings
6861        _ => Err(synth_core::Error::SynthesisError(
6862            "S-register not supported in double-precision VFP encoding".to_string(),
6863        )),
6864    }
6865}
6866
6867/// Split S-register into (Vx[3:0], qualifier_bit) for VFP encoding.
6868/// For an S-register number s: Vx = s >> 1, qualifier = s & 1.
6869/// The qualifier bit goes to D (bit 22), N (bit 7), or M (bit 5) depending on role.
6870fn encode_sreg(s: u32) -> (u32, u32) {
6871    (s >> 1, s & 1)
6872}
6873
6874/// Split D-register into (Vx[3:0], qualifier_bit) for VFP double-precision encoding.
6875/// For a D-register number d: Vx = d & 0xF, qualifier = (d >> 4) & 1.
6876/// For D0-D15, qualifier is always 0.
6877fn encode_dreg(d: u32) -> (u32, u32) {
6878    (d & 0xF, (d >> 4) & 1)
6879}
6880
6881/// Encode a VFP 3-register arithmetic instruction (VADD.F32, VSUB.F32, VMUL.F32, VDIV.F32).
6882/// Returns the full 32-bit instruction word.
6883///
6884/// VFP encoding: [cond 1110] [D opc1 Vn] [Vd 101 sz] [N opc2 M 0 Vm]
6885/// For single-precision (sz=0), coprocessor = 0xA (bits[11:8]).
6886fn encode_vfp_3reg(base: u32, sd: &VfpReg, sn: &VfpReg, sm: &VfpReg) -> Result<u32> {
6887    let sd_num = vfp_sreg_to_num(sd)?;
6888    let sn_num = vfp_sreg_to_num(sn)?;
6889    let sm_num = vfp_sreg_to_num(sm)?;
6890    let (vd, d) = encode_sreg(sd_num);
6891    let (vn, n) = encode_sreg(sn_num);
6892    let (vm, m) = encode_sreg(sm_num);
6893
6894    Ok(base | (d << 22) | (vn << 16) | (vd << 12) | (n << 7) | (m << 5) | vm)
6895}
6896
6897/// Encode a VFP 2-register instruction (VNEG.F32, VABS.F32, VSQRT.F32).
6898/// Returns the full 32-bit instruction word.
6899fn encode_vfp_2reg(base: u32, sd: &VfpReg, sm: &VfpReg) -> Result<u32> {
6900    let sd_num = vfp_sreg_to_num(sd)?;
6901    let sm_num = vfp_sreg_to_num(sm)?;
6902    let (vd, d) = encode_sreg(sd_num);
6903    let (vm, m) = encode_sreg(sm_num);
6904
6905    Ok(base | (d << 22) | (vd << 12) | (m << 5) | vm)
6906}
6907
6908/// Encode a VFP load/store (VLDR.F32 / VSTR.F32).
6909/// offset is in bytes and must be word-aligned; encoded as imm8 = offset/4.
6910/// U bit (bit 23) controls add/subtract offset.
6911fn encode_vfp_ldst(base: u32, sd: &VfpReg, addr: &MemAddr) -> Result<u32> {
6912    let sd_num = vfp_sreg_to_num(sd)?;
6913    let (vd, d) = encode_sreg(sd_num);
6914    let rn = reg_to_bits(&addr.base);
6915
6916    let offset = addr.offset;
6917    let u_bit = if offset >= 0 { 1u32 } else { 0u32 };
6918    let abs_offset = offset.unsigned_abs();
6919    let imm8 = (abs_offset / 4) & 0xFF;
6920
6921    Ok(base | (u_bit << 23) | (d << 22) | (rn << 16) | (vd << 12) | imm8)
6922}
6923
6924/// Encode VMOV between core register and S-register.
6925/// VMOV Sn, Rt: 0xEE00_0A10 | (Vn << 16) | (N << 7) | (Rt << 12)
6926/// VMOV Rt, Sn: 0xEE10_0A10 | (Vn << 16) | (N << 7) | (Rt << 12)
6927fn encode_vmov_core_sreg(to_sreg: bool, sreg: &VfpReg, core: &Reg) -> Result<u32> {
6928    let s_num = vfp_sreg_to_num(sreg)?;
6929    let (vn, n) = encode_sreg(s_num);
6930    let rt = reg_to_bits(core);
6931
6932    let base = if to_sreg { 0xEE000A10 } else { 0xEE100A10 };
6933    Ok(base | (vn << 16) | (rt << 12) | (n << 7))
6934}
6935
6936/// Encode a VFP 3-register double-precision instruction (VADD.F64, VSUB.F64, etc.).
6937/// For double-precision (sz=1), coprocessor = 0xB (bits[11:8]).
6938/// The base should have bit 8 = 1 for F64 (0xB suffix instead of 0xA).
6939fn encode_vfp_3reg_f64(base: u32, dd: &VfpReg, dn: &VfpReg, dm: &VfpReg) -> Result<u32> {
6940    let dd_num = vfp_dreg_to_num(dd)?;
6941    let dn_num = vfp_dreg_to_num(dn)?;
6942    let dm_num = vfp_dreg_to_num(dm)?;
6943    let (vd, d) = encode_dreg(dd_num);
6944    let (vn, n) = encode_dreg(dn_num);
6945    let (vm, m) = encode_dreg(dm_num);
6946
6947    Ok(base | (d << 22) | (vn << 16) | (vd << 12) | (n << 7) | (m << 5) | vm)
6948}
6949
6950/// Encode a VFP 2-register double-precision instruction (VNEG.F64, VABS.F64, VSQRT.F64).
6951fn encode_vfp_2reg_f64(base: u32, dd: &VfpReg, dm: &VfpReg) -> Result<u32> {
6952    let dd_num = vfp_dreg_to_num(dd)?;
6953    let dm_num = vfp_dreg_to_num(dm)?;
6954    let (vd, d) = encode_dreg(dd_num);
6955    let (vm, m) = encode_dreg(dm_num);
6956
6957    Ok(base | (d << 22) | (vd << 12) | (m << 5) | vm)
6958}
6959
6960/// Encode a VFP load/store for double-precision (VLDR.64 / VSTR.64).
6961/// offset is in bytes and must be word-aligned; encoded as imm8 = offset/4.
6962fn encode_vfp_ldst_f64(base: u32, dd: &VfpReg, addr: &MemAddr) -> Result<u32> {
6963    let dd_num = vfp_dreg_to_num(dd)?;
6964    let (vd, d) = encode_dreg(dd_num);
6965    let rn = reg_to_bits(&addr.base);
6966
6967    let offset = addr.offset;
6968    let u_bit = if offset >= 0 { 1u32 } else { 0u32 };
6969    let abs_offset = offset.unsigned_abs();
6970    let imm8 = (abs_offset / 4) & 0xFF;
6971
6972    Ok(base | (u_bit << 23) | (d << 22) | (rn << 16) | (vd << 12) | imm8)
6973}
6974
6975/// Encode VMOV between two core registers and a D-register.
6976/// VMOV Dm, Rt, Rt2: 0xEC40_0B10 | (Rt2 << 16) | (Rt << 12) | (M << 5) | Vm
6977/// VMOV Rt, Rt2, Dm: 0xEC50_0B10 | (Rt2 << 16) | (Rt << 12) | (M << 5) | Vm
6978fn encode_vmov_core_dreg(
6979    to_dreg: bool,
6980    dreg: &VfpReg,
6981    core_lo: &Reg,
6982    core_hi: &Reg,
6983) -> Result<u32> {
6984    let d_num = vfp_dreg_to_num(dreg)?;
6985    let (vm, m) = encode_dreg(d_num);
6986    let rt = reg_to_bits(core_lo);
6987    let rt2 = reg_to_bits(core_hi);
6988
6989    let base = if to_dreg { 0xEC400B10 } else { 0xEC500B10 };
6990    Ok(base | (rt2 << 16) | (rt << 12) | (m << 5) | vm)
6991}
6992
6993/// Emit a VFP 32-bit instruction as Thumb-2 bytes (two LE halfwords).
6994fn vfp_to_thumb_bytes(instr: u32) -> Vec<u8> {
6995    let hw1 = ((instr >> 16) & 0xFFFF) as u16;
6996    let hw2 = (instr & 0xFFFF) as u16;
6997    let mut bytes = hw1.to_le_bytes().to_vec();
6998    bytes.extend_from_slice(&hw2.to_le_bytes());
6999    bytes
7000}
7001
7002// ============================================================================
7003// Helium MVE encoding helpers
7004// ============================================================================
7005
7006/// Q-register number: Q0=0, Q1=1, ..., Q7=7
7007fn qreg_to_num(reg: &QReg) -> u32 {
7008    match reg {
7009        QReg::Q0 => 0,
7010        QReg::Q1 => 1,
7011        QReg::Q2 => 2,
7012        QReg::Q3 => 3,
7013        QReg::Q4 => 4,
7014        QReg::Q5 => 5,
7015        QReg::Q6 => 6,
7016        QReg::Q7 => 7,
7017    }
7018}
7019
7020/// MVE element size to encoding bits: S8=0b00, S16=0b01, S32=0b10
7021fn mve_size_bits(size: &MveSize) -> u32 {
7022    match size {
7023        MveSize::S8 => 0b00,
7024        MveSize::S16 => 0b01,
7025        MveSize::S32 => 0b10,
7026    }
7027}
7028
7029/// Encode MVE 3-register instruction.
7030/// Q-registers are encoded as D-register pairs: Q0=D0:D1, Q1=D2:D3, etc.
7031/// In NEON/MVE encoding, the Q-register uses D-register number = Qn * 2.
7032fn encode_mve_3reg(base: u32, qd: &QReg, qn: &QReg, qm: &QReg) -> u32 {
7033    let d = qreg_to_num(qd) * 2;
7034    let n = qreg_to_num(qn) * 2;
7035    let m = qreg_to_num(qm) * 2;
7036
7037    // Standard NEON/MVE 3-register encoding:
7038    // D bit (bit 22) = Vd[4], Vd[3:0] = bits [15:12]
7039    // N bit (bit 7)  = Vn[4], Vn[3:0] = bits [19:16]
7040    // M bit (bit 5)  = Vm[4], Vm[3:0] = bits [3:0]
7041    let vd = d & 0xF;
7042    let d_bit = (d >> 4) & 1;
7043    let vn = n & 0xF;
7044    let n_bit = (n >> 4) & 1;
7045    let vm = m & 0xF;
7046    let m_bit = (m >> 4) & 1;
7047
7048    base | (d_bit << 22) | (vn << 16) | (vd << 12) | (n_bit << 7) | (m_bit << 5) | vm
7049}
7050
7051/// Encode MVE 3-register bitwise instruction (VAND, VORR, VEOR, VBIC).
7052fn encode_mve_3reg_bitwise(base: u32, qd: &QReg, qn: &QReg, qm: &QReg) -> u32 {
7053    encode_mve_3reg(base, qd, qn, qm)
7054}
7055
7056/// Encode MVE VLDRW.32 Qd, [Rn, #offset]
7057/// Format: EC9x xxxx - contiguous load, word-sized elements
7058fn encode_mve_vldrw(qd: &QReg, addr: &MemAddr) -> u32 {
7059    let qd_enc = qreg_to_num(qd) * 2;
7060    let rn = reg_to_bits(&addr.base);
7061    let offset = addr.offset;
7062    let u_bit = if offset >= 0 { 1u32 } else { 0u32 };
7063    let abs_offset = offset.unsigned_abs();
7064    let imm7 = (abs_offset / 4) & 0x7F; // 7-bit word-aligned offset
7065
7066    // VLDRW.32 Qd, [Rn, #imm]: ED10 xx80 variant
7067    0xED100E80
7068        | (u_bit << 23)
7069        | ((qd_enc >> 4) << 22)
7070        | (rn << 16)
7071        | ((qd_enc & 0xF) << 12)
7072        | (imm7 & 0x7F)
7073}
7074
7075/// Encode MVE VSTRW.32 Qd, [Rn, #offset]
7076fn encode_mve_vstrw(qd: &QReg, addr: &MemAddr) -> u32 {
7077    let qd_enc = qreg_to_num(qd) * 2;
7078    let rn = reg_to_bits(&addr.base);
7079    let offset = addr.offset;
7080    let u_bit = if offset >= 0 { 1u32 } else { 0u32 };
7081    let abs_offset = offset.unsigned_abs();
7082    let imm7 = (abs_offset / 4) & 0x7F;
7083
7084    0xED000E80
7085        | (u_bit << 23)
7086        | ((qd_enc >> 4) << 22)
7087        | (rn << 16)
7088        | ((qd_enc & 0xF) << 12)
7089        | (imm7 & 0x7F)
7090}
7091
7092impl ArmEncoder {
7093    /// Encode MVE constant load: MOVW+MOVT+VMOV for each 32-bit word, then assemble Q-register
7094    fn encode_thumb_mve_const(&self, qd: &QReg, bytes: &[u8; 16]) -> Result<Vec<u8>> {
7095        let mut result = Vec::new();
7096        let qd_num = qreg_to_num(qd);
7097
7098        // Load each 32-bit word into R12 (temp) then VMOV into S-register
7099        for i in 0..4 {
7100            let word = u32::from_le_bytes([
7101                bytes[i * 4],
7102                bytes[i * 4 + 1],
7103                bytes[i * 4 + 2],
7104                bytes[i * 4 + 3],
7105            ]);
7106            let lo16 = word & 0xFFFF;
7107            let hi16 = (word >> 16) & 0xFFFF;
7108
7109            // MOVW R12, #lo16
7110            result.extend_from_slice(&self.encode_thumb32_movw_raw(12, lo16)?);
7111            // MOVT R12, #hi16
7112            if hi16 != 0 {
7113                result.extend_from_slice(&self.encode_thumb32_movt_raw(12, hi16)?);
7114            }
7115
7116            // VMOV Sn, R12 where Sn = Qd*4 + i
7117            let s_num = qd_num * 4 + i as u32;
7118            let (vn, n) = encode_sreg(s_num);
7119            let vmov: u32 = 0xEE000A10 | (vn << 16) | (12 << 12) | (n << 7);
7120            result.extend_from_slice(&vfp_to_thumb_bytes(vmov));
7121        }
7122
7123        Ok(result)
7124    }
7125
7126    /// Encode lane-wise f32 binary operation (VDIV, etc.) via S-register extraction
7127    fn encode_thumb_mve_lane_wise_f32_binop(
7128        &self,
7129        qd: &QReg,
7130        qn: &QReg,
7131        qm: &QReg,
7132        vfp_base: u32,
7133    ) -> Result<Vec<u8>> {
7134        let mut result = Vec::new();
7135        let qd_num = qreg_to_num(qd);
7136        let qn_num = qreg_to_num(qn);
7137        let qm_num = qreg_to_num(qm);
7138
7139        // For each lane 0..3: use S-registers directly (Q aliasing)
7140        for i in 0..4u32 {
7141            let sd = qd_num * 4 + i;
7142            let sn = qn_num * 4 + i;
7143            let sm = qm_num * 4 + i;
7144
7145            let (vd, d) = encode_sreg(sd);
7146            let (vn, n) = encode_sreg(sn);
7147            let (vm, m) = encode_sreg(sm);
7148
7149            let instr = vfp_base | (d << 22) | (vn << 16) | (vd << 12) | (n << 7) | (m << 5) | vm;
7150            result.extend_from_slice(&vfp_to_thumb_bytes(instr));
7151        }
7152
7153        Ok(result)
7154    }
7155
7156    /// Encode lane-wise f32 VSQRT via S-register extraction
7157    fn encode_thumb_mve_lane_wise_f32_sqrt(&self, qd: &QReg, qm: &QReg) -> Result<Vec<u8>> {
7158        let mut result = Vec::new();
7159        let qd_num = qreg_to_num(qd);
7160        let qm_num = qreg_to_num(qm);
7161
7162        // VSQRT.F32 base: 0xEEB10AC0
7163        for i in 0..4u32 {
7164            let sd = qd_num * 4 + i;
7165            let sm = qm_num * 4 + i;
7166
7167            let (vd, d) = encode_sreg(sd);
7168            let (vm, m) = encode_sreg(sm);
7169
7170            let instr: u32 = 0xEEB10AC0 | (d << 22) | (vd << 12) | (m << 5) | vm;
7171            result.extend_from_slice(&vfp_to_thumb_bytes(instr));
7172        }
7173
7174        Ok(result)
7175    }
7176}
7177
7178#[cfg(test)]
7179mod tests {
7180    use super::*;
7181
7182    #[test]
7183    fn test_encoder_creation() {
7184        let encoder_arm = ArmEncoder::new_arm32();
7185        assert!(!encoder_arm.thumb_mode);
7186
7187        let encoder_thumb = ArmEncoder::new_thumb2();
7188        assert!(encoder_thumb.thumb_mode);
7189    }
7190
7191    /// #204 WAKE-path regression: `SetCond` materialized 0/1 with the 16-bit
7192    /// `MOVS Rd,#imm` (T1), whose Rd field is 3 bits (R0–R7). For a high Rd
7193    /// (R8–R12) `rd_bits << 8` overflows bit 11, flipping the opcode MOVS→CMP
7194    /// (`0x2c00`), so the boolean was never written — gale's `has_waiter` kept a
7195    /// stale value and the binary-sem WAKE dispatch read garbage. High Rd must
7196    /// use the 32-bit `MOV.W` (T2). Verify the bytes, not the IR.
7197    #[test]
7198    fn test_encode_setcond_high_reg_uses_mov_w_204() {
7199        use synth_synthesis::{ArmOp, Condition, Reg};
7200        let enc = ArmEncoder::new_thumb2();
7201        // R12 (high): must be ITE + MOV.W #1 + MOV.W #0, never a 16-bit MOVS/CMP.
7202        let hi = enc
7203            .encode(&ArmOp::SetCond {
7204                rd: Reg::R12,
7205                cond: Condition::NE,
7206            })
7207            .unwrap();
7208        assert_eq!(hi.len(), 10, "ITE(2) + MOV.W(4) + MOV.W(4): {hi:02x?}");
7209        // both value halfwords are MOV.W (0xF04F) — NOT the corrupt CMP (0x2c..).
7210        assert_eq!(&hi[2..4], &[0x4F, 0xF0], "then = MOV.W: {hi:02x?}");
7211        assert_eq!(&hi[6..8], &[0x4F, 0xF0], "else = MOV.W: {hi:02x?}");
7212        assert_eq!(hi[4] & 0x0F, 0x01, "then imm = #1");
7213        assert_eq!(hi[8] & 0x0F, 0x00, "else imm = #0");
7214        // Low Rd keeps the compact 16-bit MOVS form.
7215        let lo = enc
7216            .encode(&ArmOp::SetCond {
7217                rd: Reg::R0,
7218                cond: Condition::NE,
7219            })
7220            .unwrap();
7221        assert_eq!(lo.len(), 6, "ITE(2) + MOVS(2) + MOVS(2): {lo:02x?}");
7222        assert_eq!(lo[2..4], [0x01, 0x20], "then = MOVS R0,#1");
7223        assert_eq!(lo[4..6], [0x00, 0x20], "else = MOVS R0,#0");
7224    }
7225
7226    /// #209 Opt 1b: UMULL RdLo, RdHi, Rn, Rm encodes correctly on both ISAs.
7227    /// Thumb-2 T1: 1111 1011 1010 Rn | RdLo RdHi 0000 Rm.
7228    /// A32:        cond 0000 1000 RdHi RdLo Rm 1001 Rn.
7229    #[test]
7230    fn test_encode_umull_209b() {
7231        use synth_synthesis::{ArmOp, Reg};
7232        let op = ArmOp::Umull {
7233            rdlo: Reg::R4,
7234            rdhi: Reg::R5,
7235            rn: Reg::R0,
7236            rm: Reg::R3,
7237        };
7238        // Thumb-2: hw1 = 0xFBA0 | 0 = 0xFBA0; hw2 = (4<<12)|(5<<8)|3 = 0x4503.
7239        let t = ArmEncoder::new_thumb2().encode(&op).unwrap();
7240        assert_eq!(
7241            t,
7242            vec![0xA0, 0xFB, 0x03, 0x45],
7243            "umull r4,r5,r0,r3 (T2): {t:02x?}"
7244        );
7245        // A32: 0xE0800090 | (5<<16) | (4<<12) | (3<<8) | 0 = 0xE0854390.
7246        let a = ArmEncoder::new_arm32().encode(&op).unwrap();
7247        assert_eq!(
7248            a,
7249            0xE085_4390u32.to_le_bytes().to_vec(),
7250            "umull (A32): {a:02x?}"
7251        );
7252    }
7253
7254    /// #206 regression: the ARM32 (A32) `Ldr`/`Str` encoders fed `addr` through
7255    /// `encode_mem_addr`, which returns only the 12-bit immediate — so a register
7256    /// offset (`[rn, rm, #off]`) was silently dropped to `[rn, #off]`, sending
7257    /// the access to the wrong runtime address (silent miscompile on the default
7258    /// `--target arm`). A register offset must materialize `ip = rn + rm` and
7259    /// load from `[ip, #off]`. Verify the bytes.
7260    #[test]
7261    fn test_encode_arm32_indexed_load_keeps_index_206() {
7262        use synth_synthesis::{ArmOp, MemAddr, Reg};
7263        let enc = ArmEncoder::new_arm32();
7264        // ldr r0, [r11, r1, #8]  must NOT collapse to a single immediate ldr.
7265        let bytes = enc
7266            .encode(&ArmOp::Ldr {
7267                rd: Reg::R0,
7268                addr: MemAddr::reg_imm(Reg::R11, Reg::R1, 8),
7269            })
7270            .unwrap();
7271        assert_eq!(
7272            bytes.len(),
7273            8,
7274            "expected ADD ip + LDR (2 words): {bytes:02x?}"
7275        );
7276        let add = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
7277        let ldr = u32::from_le_bytes(bytes[4..8].try_into().unwrap());
7278        // ADD ip, r11, r1  = 0xE08BC001
7279        assert_eq!(add, 0xE08B_C001, "ADD ip,r11,r1: {add:#010x}");
7280        // LDR r0, [ip, #8] = 0xE59C0008
7281        assert_eq!(ldr, 0xE59C_0008, "LDR r0,[ip,#8]: {ldr:#010x}");
7282        // A bare immediate ldr (the bug) would be 0xE59B0008 (base=r11) — reject.
7283        assert_ne!(ldr, 0xE59B_0008, "index must not be dropped");
7284    }
7285
7286    /// #178/#180 regression: the Thumb `Add`/`Adds`/`Subs` reg-forms used the
7287    /// 16-bit encoding unconditionally. For high registers (R12 base scratch,
7288    /// R8-R11 i64 pairs) the 3-bit register fields overflow and corrupt the
7289    /// operands — `add ip,ip,r0` came out as `adds r4,r5,r1` (0x186C), silently
7290    /// dropping the address operand and miscompiling every optimized memory
7291    /// access. High registers must use the 32-bit `.W` forms.
7292    #[test]
7293    fn test_encode_thumb_add_high_reg_uses_add_w_178_180() {
7294        let encoder = ArmEncoder::new_thumb2();
7295
7296        // add ip, ip, r0  — the exact MemLoad/MemStore base+addr op.
7297        let code = encoder
7298            .encode(&ArmOp::Add {
7299                rd: Reg::R12,
7300                rn: Reg::R12,
7301                op2: Operand2::Reg(Reg::R0),
7302            })
7303            .unwrap();
7304        // ADD.W ip, ip, r0 = EB0C 0C00 (little-endian halfwords).
7305        assert_eq!(
7306            code,
7307            vec![0x0C, 0xEB, 0x00, 0x0C],
7308            "high-reg Thumb ADD must be 32-bit ADD.W (EB0C 0C00), not corrupt 16-bit; got {code:02X?}"
7309        );
7310        // Must NOT be the buggy 16-bit 0x186C (`adds r4,r5,r1`).
7311        assert_ne!(code, vec![0x6C, 0x18], "regressed to corrupt 16-bit ADDS");
7312
7313        // Low-register add stays 16-bit (no regression for the common case).
7314        let lo = encoder
7315            .encode(&ArmOp::Add {
7316                rd: Reg::R1,
7317                rn: Reg::R2,
7318                op2: Operand2::Reg(Reg::R3),
7319            })
7320            .unwrap();
7321        assert_eq!(
7322            lo.len(),
7323            2,
7324            "low-reg ADD should remain 16-bit, got {lo:02X?}"
7325        );
7326    }
7327
7328    /// #178/#180 sibling: i64 low-word `Adds`/`Subs` can land in R8-R11 pairs;
7329    /// those must fall back to 32-bit ADDS.W/SUBS.W (flag-setting preserved).
7330    #[test]
7331    fn test_encode_thumb_adds_subs_high_reg_use_32bit_178_180() {
7332        let encoder = ArmEncoder::new_thumb2();
7333
7334        // adds r10, r10, r8  → ADDS.W = EB1A 0A08
7335        let adds = encoder
7336            .encode(&ArmOp::Adds {
7337                rd: Reg::R10,
7338                rn: Reg::R10,
7339                op2: Operand2::Reg(Reg::R8),
7340            })
7341            .unwrap();
7342        assert_eq!(
7343            adds,
7344            vec![0x1A, 0xEB, 0x08, 0x0A],
7345            "high-reg ADDS must be 32-bit ADDS.W (EB1A 0A08); got {adds:02X?}"
7346        );
7347
7348        // subs r10, r10, r8  → SUBS.W = EBBA 0A08
7349        let subs = encoder
7350            .encode(&ArmOp::Subs {
7351                rd: Reg::R10,
7352                rn: Reg::R10,
7353                op2: Operand2::Reg(Reg::R8),
7354            })
7355            .unwrap();
7356        assert_eq!(
7357            subs,
7358            vec![0xBA, 0xEB, 0x08, 0x0A],
7359            "high-reg SUBS must be 32-bit SUBS.W (EBBA 0A08); got {subs:02X?}"
7360        );
7361    }
7362
7363    /// #184 (sibling of #180): 16-bit CMN (T1) only encodes R0-R7. High registers
7364    /// must use 32-bit CMN.W, not the corrupt truncated 16-bit form.
7365    #[test]
7366    fn test_encode_thumb_cmn_high_reg_uses_cmn_w_184() {
7367        let encoder = ArmEncoder::new_thumb2();
7368
7369        // cmn r10, r8  → CMN.W = EB1A 0F08 (ADD.W S=1, Rd=PC discarded).
7370        let cmn = encoder
7371            .encode(&ArmOp::Cmn {
7372                rn: Reg::R10,
7373                op2: Operand2::Reg(Reg::R8),
7374            })
7375            .unwrap();
7376        assert_eq!(
7377            cmn,
7378            vec![0x1A, 0xEB, 0x08, 0x0F],
7379            "high-reg CMN must be 32-bit CMN.W (EB1A 0F08); got {cmn:02X?}"
7380        );
7381
7382        // Low registers stay 16-bit: cmn r1, r2 = 0x42D1.
7383        let lo = encoder
7384            .encode(&ArmOp::Cmn {
7385                rn: Reg::R1,
7386                op2: Operand2::Reg(Reg::R2),
7387            })
7388            .unwrap();
7389        assert_eq!(
7390            lo.len(),
7391            2,
7392            "low-reg CMN should remain 16-bit, got {lo:02X?}"
7393        );
7394        assert_eq!(lo, vec![0xD1, 0x42], "low-reg CMN bytes wrong: {lo:02X?}");
7395    }
7396
7397    /// #185 regression: feeding PC (R15) as a data operand to a Thumb-2 op that
7398    /// guards its registers must return Err, not panic under debug-assertions.
7399    /// (Synth never emits PC here; the fuzz harness requires encode() be total.)
7400    #[test]
7401    fn test_encode_pc_operand_returns_err_not_panic_185() {
7402        let encoder = ArmEncoder::new_thumb2();
7403        for op in [
7404            ArmOp::Sdiv {
7405                rd: Reg::PC,
7406                rn: Reg::R0,
7407                rm: Reg::R1,
7408            },
7409            ArmOp::Udiv {
7410                rd: Reg::R0,
7411                rn: Reg::PC,
7412                rm: Reg::R1,
7413            },
7414            ArmOp::Sdiv {
7415                rd: Reg::R0,
7416                rn: Reg::R1,
7417                rm: Reg::PC,
7418            },
7419        ] {
7420            let r = encoder.encode(&op);
7421            assert!(
7422                r.is_err(),
7423                "encode({op:?}) must return Err for a PC operand, got {r:?}"
7424            );
7425        }
7426        // Valid registers still encode fine (no false rejection).
7427        assert!(
7428            encoder
7429                .encode(&ArmOp::Sdiv {
7430                    rd: Reg::R0,
7431                    rn: Reg::R1,
7432                    rm: Reg::R2
7433                })
7434                .is_ok()
7435        );
7436    }
7437
7438    #[test]
7439    fn test_encode_nop_arm32() {
7440        let encoder = ArmEncoder::new_arm32();
7441        let code = encoder.encode(&ArmOp::Nop).unwrap();
7442
7443        assert_eq!(code.len(), 4); // ARM32 instructions are 4 bytes
7444        assert_eq!(code, vec![0x00, 0x00, 0xA0, 0xE1]); // MOV R0, R0
7445    }
7446
7447    #[test]
7448    fn test_encode_nop_thumb() {
7449        let encoder = ArmEncoder::new_thumb2();
7450        let code = encoder.encode(&ArmOp::Nop).unwrap();
7451
7452        assert_eq!(code.len(), 2); // Thumb instructions are 2 bytes
7453        assert_eq!(code, vec![0x00, 0xBF]); // NOP
7454    }
7455
7456    #[test]
7457    fn test_encode_mov_immediate_arm32() {
7458        let encoder = ArmEncoder::new_arm32();
7459        let op = ArmOp::Mov {
7460            rd: Reg::R0,
7461            op2: Operand2::Imm(42),
7462        };
7463
7464        let code = encoder.encode(&op).unwrap();
7465        assert_eq!(code.len(), 4);
7466
7467        // Verify it's a MOV instruction (bits should have immediate flag set)
7468        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
7469        assert_eq!(instr & 0x0E000000, 0x02000000); // Check I bit is set
7470    }
7471
7472    #[test]
7473    fn test_encode_add_registers_arm32() {
7474        let encoder = ArmEncoder::new_arm32();
7475        let op = ArmOp::Add {
7476            rd: Reg::R0,
7477            rn: Reg::R1,
7478            op2: Operand2::Reg(Reg::R2),
7479        };
7480
7481        let code = encoder.encode(&op).unwrap();
7482        assert_eq!(code.len(), 4);
7483
7484        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
7485        // Verify it's an ADD instruction with correct opcode
7486        assert_eq!(instr & 0x0FE00000, 0x00800000);
7487    }
7488
7489    #[test]
7490    fn test_encode_ldr_arm32() {
7491        let encoder = ArmEncoder::new_arm32();
7492        let op = ArmOp::Ldr {
7493            rd: Reg::R0,
7494            addr: MemAddr::imm(Reg::R1, 4),
7495        };
7496
7497        let code = encoder.encode(&op).unwrap();
7498        assert_eq!(code.len(), 4);
7499
7500        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
7501        // Verify load bit is set
7502        assert_eq!(instr & 0x00100000, 0x00100000);
7503    }
7504
7505    #[test]
7506    fn test_encode_str_arm32() {
7507        let encoder = ArmEncoder::new_arm32();
7508        let op = ArmOp::Str {
7509            rd: Reg::R0,
7510            addr: MemAddr::imm(Reg::SP, 0),
7511        };
7512
7513        let code = encoder.encode(&op).unwrap();
7514        assert_eq!(code.len(), 4);
7515    }
7516
7517    #[test]
7518    fn test_encode_branch_arm32() {
7519        let encoder = ArmEncoder::new_arm32();
7520        let op = ArmOp::Bl {
7521            label: "main".to_string(),
7522        };
7523
7524        let code = encoder.encode(&op).unwrap();
7525        assert_eq!(code.len(), 4);
7526
7527        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
7528        // Verify BL opcode
7529        assert_eq!(instr & 0x0F000000, 0x0B000000);
7530    }
7531
7532    /// Regression test for #167 + #174: the Thumb-2 BL relocatable placeholder
7533    /// must carry a -4 addend so an R_ARM_THM_CALL nets to exactly the symbol S.
7534    /// The correct encoding is what `gas` emits for `bl <extern>`: f7ff fffe
7535    /// (hw1=0xF7FF, hw2=0xFFFE), little-endian bytes FF F7 FE FF.
7536    ///   - 0xD000 (J1=J2=0) → ~+0x600000 garbage addend: `bl c0000c` / truncated
7537    ///     to fit (#167).
7538    ///   - 0xF800 (addend 0) → lands at S+4, one instruction past the callee
7539    ///     entry (#174).
7540    ///   - 0xFFFE (addend -4) → lands at S. Correct.
7541    #[test]
7542    fn test_encode_thumb_bl_placeholder_addend_167_174() {
7543        let encoder = ArmEncoder::new_thumb2();
7544        let op = ArmOp::Bl {
7545            label: "callee".to_string(),
7546        };
7547
7548        let code = encoder.encode(&op).unwrap();
7549        assert_eq!(code.len(), 4, "Thumb-2 BL is 32-bit");
7550
7551        let hw1 = u16::from_le_bytes([code[0], code[1]]);
7552        let hw2 = u16::from_le_bytes([code[2], code[3]]);
7553        assert_eq!(hw1, 0xF7FF, "BL first halfword (matches gas `bl <extern>`)");
7554        assert_eq!(
7555            hw2, 0xFFFE,
7556            "BL second halfword must be 0xFFFE (-4 addend → nets to S), not 0xF800 (→ S+4, #174) or 0xD000 (#167)"
7557        );
7558        assert_ne!(hw2, 0xF800, "0xF800 (addend 0) lands at S+4 (#174)");
7559        assert_ne!(hw2, 0xD000, "0xD000 bakes in a ~+0x600000 addend (#167)");
7560    }
7561
7562    #[test]
7563    fn test_encode_sequence() {
7564        let encoder = ArmEncoder::new_arm32();
7565        let ops = vec![
7566            ArmOp::Mov {
7567                rd: Reg::R0,
7568                op2: Operand2::Imm(42),
7569            },
7570            ArmOp::Mov {
7571                rd: Reg::R1,
7572                op2: Operand2::Imm(10),
7573            },
7574            ArmOp::Add {
7575                rd: Reg::R2,
7576                rn: Reg::R0,
7577                op2: Operand2::Reg(Reg::R1),
7578            },
7579        ];
7580
7581        let code = encoder.encode_sequence(&ops).unwrap();
7582        assert_eq!(code.len(), 12); // 3 instructions * 4 bytes
7583    }
7584
7585    #[test]
7586    fn test_reg_to_bits() {
7587        assert_eq!(reg_to_bits(&Reg::R0), 0);
7588        assert_eq!(reg_to_bits(&Reg::R7), 7);
7589        assert_eq!(reg_to_bits(&Reg::SP), 13);
7590        assert_eq!(reg_to_bits(&Reg::LR), 14);
7591        assert_eq!(reg_to_bits(&Reg::PC), 15);
7592    }
7593
7594    #[test]
7595    fn test_encode_bitwise_operations() {
7596        let encoder = ArmEncoder::new_arm32();
7597
7598        let and_op = ArmOp::And {
7599            rd: Reg::R0,
7600            rn: Reg::R1,
7601            op2: Operand2::Reg(Reg::R2),
7602        };
7603        let and_code = encoder.encode(&and_op).unwrap();
7604        assert_eq!(and_code.len(), 4);
7605
7606        let orr_op = ArmOp::Orr {
7607            rd: Reg::R0,
7608            rn: Reg::R1,
7609            op2: Operand2::Reg(Reg::R2),
7610        };
7611        let orr_code = encoder.encode(&orr_op).unwrap();
7612        assert_eq!(orr_code.len(), 4);
7613
7614        let eor_op = ArmOp::Eor {
7615            rd: Reg::R0,
7616            rn: Reg::R1,
7617            op2: Operand2::Reg(Reg::R2),
7618        };
7619        let eor_code = encoder.encode(&eor_op).unwrap();
7620        assert_eq!(eor_code.len(), 4);
7621    }
7622
7623    // === Thumb-2 32-bit encoding tests ===
7624
7625    #[test]
7626    fn test_encode_sdiv_thumb2() {
7627        let encoder = ArmEncoder::new_thumb2();
7628        let op = ArmOp::Sdiv {
7629            rd: Reg::R0,
7630            rn: Reg::R1,
7631            rm: Reg::R2,
7632        };
7633
7634        let code = encoder.encode(&op).unwrap();
7635        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
7636
7637        // SDIV R0, R1, R2: 0xFB91 0xF0F2
7638        // First halfword: 0xFB90 | Rn(1) = 0xFB91
7639        // Second halfword: 0xF0F0 | Rd(0)<<8 | Rm(2) = 0xF0F2
7640        // Little-endian: [0x91, 0xFB, 0xF2, 0xF0]
7641        assert_eq!(code[0], 0x91);
7642        assert_eq!(code[1], 0xFB);
7643        assert_eq!(code[2], 0xF2);
7644        assert_eq!(code[3], 0xF0);
7645    }
7646
7647    #[test]
7648    fn test_encode_udiv_thumb2() {
7649        let encoder = ArmEncoder::new_thumb2();
7650        let op = ArmOp::Udiv {
7651            rd: Reg::R0,
7652            rn: Reg::R1,
7653            rm: Reg::R2,
7654        };
7655
7656        let code = encoder.encode(&op).unwrap();
7657        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
7658
7659        // UDIV R0, R1, R2: 0xFBB1 0xF0F2
7660        // Little-endian: [0xB1, 0xFB, 0xF2, 0xF0]
7661        assert_eq!(code[0], 0xB1);
7662        assert_eq!(code[1], 0xFB);
7663        assert_eq!(code[2], 0xF2);
7664        assert_eq!(code[3], 0xF0);
7665    }
7666
7667    #[test]
7668    fn test_encode_mul_thumb2() {
7669        let encoder = ArmEncoder::new_thumb2();
7670        let op = ArmOp::Mul {
7671            rd: Reg::R0,
7672            rn: Reg::R1,
7673            rm: Reg::R2,
7674        };
7675
7676        let code = encoder.encode(&op).unwrap();
7677        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
7678    }
7679
7680    #[test]
7681    fn test_encode_and_thumb2() {
7682        let encoder = ArmEncoder::new_thumb2();
7683        let op = ArmOp::And {
7684            rd: Reg::R0,
7685            rn: Reg::R1,
7686            op2: Operand2::Reg(Reg::R2),
7687        };
7688
7689        let code = encoder.encode(&op).unwrap();
7690        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
7691    }
7692
7693    #[test]
7694    fn test_encode_lsl_thumb2_low_regs() {
7695        let encoder = ArmEncoder::new_thumb2();
7696        let op = ArmOp::Lsl {
7697            rd: Reg::R0,
7698            rn: Reg::R1,
7699            shift: 5,
7700        };
7701
7702        let code = encoder.encode(&op).unwrap();
7703        assert_eq!(code.len(), 2); // 16-bit for low registers
7704    }
7705
7706    #[test]
7707    fn test_encode_clz_thumb2() {
7708        let encoder = ArmEncoder::new_thumb2();
7709        let op = ArmOp::Clz {
7710            rd: Reg::R0,
7711            rm: Reg::R1,
7712        };
7713
7714        let code = encoder.encode(&op).unwrap();
7715        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
7716    }
7717
7718    #[test]
7719    fn test_encode_bx_thumb2() {
7720        let encoder = ArmEncoder::new_thumb2();
7721        let op = ArmOp::Bx { rm: Reg::LR };
7722
7723        let code = encoder.encode(&op).unwrap();
7724        assert_eq!(code.len(), 2); // 16-bit instruction
7725
7726        // BX LR: 0x4770
7727        assert_eq!(code, vec![0x70, 0x47]);
7728    }
7729
7730    // ========================================================================
7731    // f32 pseudo-op encoding tests
7732    // ========================================================================
7733
7734    #[test]
7735    fn test_encode_f32_abs_arm32() {
7736        let encoder = ArmEncoder::new_arm32();
7737        let op = ArmOp::F32Abs {
7738            sd: VfpReg::S0,
7739            sm: VfpReg::S2,
7740        };
7741        let code = encoder.encode(&op).unwrap();
7742        assert_eq!(code.len(), 4); // Single VFP instruction
7743    }
7744
7745    #[test]
7746    fn test_encode_f32_neg_arm32() {
7747        let encoder = ArmEncoder::new_arm32();
7748        let op = ArmOp::F32Neg {
7749            sd: VfpReg::S0,
7750            sm: VfpReg::S2,
7751        };
7752        let code = encoder.encode(&op).unwrap();
7753        assert_eq!(code.len(), 4);
7754    }
7755
7756    #[test]
7757    fn test_encode_f32_sqrt_arm32() {
7758        let encoder = ArmEncoder::new_arm32();
7759        let op = ArmOp::F32Sqrt {
7760            sd: VfpReg::S0,
7761            sm: VfpReg::S2,
7762        };
7763        let code = encoder.encode(&op).unwrap();
7764        assert_eq!(code.len(), 4);
7765    }
7766
7767    #[test]
7768    fn test_encode_f32_ceil_arm32() {
7769        let encoder = ArmEncoder::new_arm32();
7770        let op = ArmOp::F32Ceil {
7771            sd: VfpReg::S0,
7772            sm: VfpReg::S2,
7773        };
7774        let code = encoder.encode(&op).unwrap();
7775        // VMRS + BIC + ORR + VMSR + VCVT.S32.F32 + VMRS + BIC + VMSR + VCVT.F32.S32
7776        assert_eq!(code.len(), 36);
7777    }
7778
7779    #[test]
7780    fn test_encode_f32_floor_thumb2() {
7781        let encoder = ArmEncoder::new_thumb2();
7782        let op = ArmOp::F32Floor {
7783            sd: VfpReg::S0,
7784            sm: VfpReg::S2,
7785        };
7786        let code = encoder.encode(&op).unwrap();
7787        // VMRS + BIC.W + ORR.W + VMSR + VCVT + VMRS + BIC.W + VMSR + VCVT.F32.S32
7788        assert_eq!(code.len(), 36);
7789    }
7790
7791    #[test]
7792    fn test_encode_f32_min_arm32() {
7793        let encoder = ArmEncoder::new_arm32();
7794        let op = ArmOp::F32Min {
7795            sd: VfpReg::S0,
7796            sn: VfpReg::S2,
7797            sm: VfpReg::S4,
7798        };
7799        let code = encoder.encode(&op).unwrap();
7800        assert_eq!(code.len(), 16); // VMOV + VCMP + VMRS + conditional VMOV
7801    }
7802
7803    #[test]
7804    fn test_encode_f32_max_thumb2() {
7805        let encoder = ArmEncoder::new_thumb2();
7806        let op = ArmOp::F32Max {
7807            sd: VfpReg::S0,
7808            sn: VfpReg::S2,
7809            sm: VfpReg::S4,
7810        };
7811        let code = encoder.encode(&op).unwrap();
7812        // VMOV(4) + VCMP(4) + VMRS(4) + IT(2) + VMOV(4) = 18
7813        assert_eq!(code.len(), 18);
7814    }
7815
7816    #[test]
7817    fn test_encode_f32_copysign_arm32() {
7818        let encoder = ArmEncoder::new_arm32();
7819        let op = ArmOp::F32Copysign {
7820            sd: VfpReg::S0,
7821            sn: VfpReg::S2,
7822            sm: VfpReg::S4,
7823        };
7824        let code = encoder.encode(&op).unwrap();
7825        // VMOV + VMOV + AND + BIC + ORR + VMOV = 6 * 4 = 24
7826        assert_eq!(code.len(), 24);
7827    }
7828
7829    // ========================================================================
7830    // f64 encoding tests
7831    // ========================================================================
7832
7833    #[test]
7834    fn test_encode_f64_add_arm32() {
7835        let encoder = ArmEncoder::new_arm32();
7836        let op = ArmOp::F64Add {
7837            dd: VfpReg::D0,
7838            dn: VfpReg::D1,
7839            dm: VfpReg::D2,
7840        };
7841        let code = encoder.encode(&op).unwrap();
7842        assert_eq!(code.len(), 4);
7843        // VADD.F64 D0, D1, D2: check coprocessor is cp11 (0xB)
7844        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
7845        assert_eq!((instr >> 8) & 0xF, 0xB); // cp11
7846    }
7847
7848    #[test]
7849    fn test_encode_f64_sub_thumb2() {
7850        let encoder = ArmEncoder::new_thumb2();
7851        let op = ArmOp::F64Sub {
7852            dd: VfpReg::D0,
7853            dn: VfpReg::D1,
7854            dm: VfpReg::D2,
7855        };
7856        let code = encoder.encode(&op).unwrap();
7857        assert_eq!(code.len(), 4); // 32-bit VFP as two Thumb halfwords
7858    }
7859
7860    #[test]
7861    fn test_encode_f64_mul_arm32() {
7862        let encoder = ArmEncoder::new_arm32();
7863        let op = ArmOp::F64Mul {
7864            dd: VfpReg::D0,
7865            dn: VfpReg::D1,
7866            dm: VfpReg::D2,
7867        };
7868        let code = encoder.encode(&op).unwrap();
7869        assert_eq!(code.len(), 4);
7870    }
7871
7872    #[test]
7873    fn test_encode_f64_div_arm32() {
7874        let encoder = ArmEncoder::new_arm32();
7875        let op = ArmOp::F64Div {
7876            dd: VfpReg::D0,
7877            dn: VfpReg::D1,
7878            dm: VfpReg::D2,
7879        };
7880        let code = encoder.encode(&op).unwrap();
7881        assert_eq!(code.len(), 4);
7882    }
7883
7884    #[test]
7885    fn test_encode_f64_abs_arm32() {
7886        let encoder = ArmEncoder::new_arm32();
7887        let op = ArmOp::F64Abs {
7888            dd: VfpReg::D0,
7889            dm: VfpReg::D2,
7890        };
7891        let code = encoder.encode(&op).unwrap();
7892        assert_eq!(code.len(), 4);
7893    }
7894
7895    #[test]
7896    fn test_encode_f64_neg_arm32() {
7897        let encoder = ArmEncoder::new_arm32();
7898        let op = ArmOp::F64Neg {
7899            dd: VfpReg::D0,
7900            dm: VfpReg::D2,
7901        };
7902        let code = encoder.encode(&op).unwrap();
7903        assert_eq!(code.len(), 4);
7904    }
7905
7906    #[test]
7907    fn test_encode_f64_sqrt_arm32() {
7908        let encoder = ArmEncoder::new_arm32();
7909        let op = ArmOp::F64Sqrt {
7910            dd: VfpReg::D0,
7911            dm: VfpReg::D2,
7912        };
7913        let code = encoder.encode(&op).unwrap();
7914        assert_eq!(code.len(), 4);
7915    }
7916
7917    #[test]
7918    fn test_encode_f64_load_arm32() {
7919        let encoder = ArmEncoder::new_arm32();
7920        let op = ArmOp::F64Load {
7921            dd: VfpReg::D0,
7922            addr: MemAddr::imm(Reg::R0, 8),
7923        };
7924        let code = encoder.encode(&op).unwrap();
7925        assert_eq!(code.len(), 4);
7926        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
7927        assert_eq!((instr >> 8) & 0xF, 0xB); // cp11 for F64
7928        assert_eq!(instr & 0xFF, 2); // offset 8 / 4 = 2
7929    }
7930
7931    #[test]
7932    fn test_encode_f64_store_thumb2() {
7933        let encoder = ArmEncoder::new_thumb2();
7934        let op = ArmOp::F64Store {
7935            dd: VfpReg::D0,
7936            addr: MemAddr::imm(Reg::SP, 0),
7937        };
7938        let code = encoder.encode(&op).unwrap();
7939        assert_eq!(code.len(), 4);
7940    }
7941
7942    #[test]
7943    fn test_encode_f64_compare_arm32() {
7944        let encoder = ArmEncoder::new_arm32();
7945        let op = ArmOp::F64Eq {
7946            rd: Reg::R0,
7947            dn: VfpReg::D0,
7948            dm: VfpReg::D1,
7949        };
7950        let code = encoder.encode(&op).unwrap();
7951        assert_eq!(code.len(), 16); // VCMP + VMRS + MOV #0 + MOVcond #1
7952    }
7953
7954    #[test]
7955    fn test_encode_f64_compare_thumb2() {
7956        let encoder = ArmEncoder::new_thumb2();
7957        let op = ArmOp::F64Lt {
7958            rd: Reg::R0,
7959            dn: VfpReg::D0,
7960            dm: VfpReg::D1,
7961        };
7962        let code = encoder.encode(&op).unwrap();
7963        // VCMP(4) + VMRS(4) + MOVS(2) + IT(2) + MOV(2) = 14
7964        assert_eq!(code.len(), 14);
7965    }
7966
7967    #[test]
7968    fn test_encode_f64_const_arm32() {
7969        let encoder = ArmEncoder::new_arm32();
7970        let op = ArmOp::F64Const {
7971            dd: VfpReg::D0,
7972            value: 3.125,
7973        };
7974        let code = encoder.encode(&op).unwrap();
7975        // MOVW(4) + MOVT(4) + MOVW(4) + MOVT(4) + VMOV(4) = 20
7976        assert_eq!(code.len(), 20);
7977    }
7978
7979    #[test]
7980    fn test_encode_f64_const_thumb2() {
7981        let encoder = ArmEncoder::new_thumb2();
7982        let op = ArmOp::F64Const {
7983            dd: VfpReg::D0,
7984            value: 2.5,
7985        };
7986        let code = encoder.encode(&op).unwrap();
7987        // MOVW(4) + MOVT(4) + MOVW(4) + MOVT(4) + VMOV(4) = 20
7988        assert_eq!(code.len(), 20);
7989    }
7990
7991    #[test]
7992    fn test_encode_f64_convert_i32s_arm32() {
7993        let encoder = ArmEncoder::new_arm32();
7994        let op = ArmOp::F64ConvertI32S {
7995            dd: VfpReg::D0,
7996            rm: Reg::R0,
7997        };
7998        let code = encoder.encode(&op).unwrap();
7999        // VMOV(4) + VCVT(4) = 8
8000        assert_eq!(code.len(), 8);
8001    }
8002
8003    #[test]
8004    fn test_encode_f64_promote_f32_arm32() {
8005        let encoder = ArmEncoder::new_arm32();
8006        let op = ArmOp::F64PromoteF32 {
8007            dd: VfpReg::D0,
8008            sm: VfpReg::S0,
8009        };
8010        let code = encoder.encode(&op).unwrap();
8011        assert_eq!(code.len(), 4); // Single VCVT.F64.F32 instruction
8012    }
8013
8014    #[test]
8015    fn test_encode_f64_promote_f32_thumb2() {
8016        let encoder = ArmEncoder::new_thumb2();
8017        let op = ArmOp::F64PromoteF32 {
8018            dd: VfpReg::D0,
8019            sm: VfpReg::S0,
8020        };
8021        let code = encoder.encode(&op).unwrap();
8022        assert_eq!(code.len(), 4);
8023    }
8024
8025    #[test]
8026    fn test_encode_i32_trunc_f64s_arm32() {
8027        let encoder = ArmEncoder::new_arm32();
8028        let op = ArmOp::I32TruncF64S {
8029            rd: Reg::R0,
8030            dm: VfpReg::D0,
8031        };
8032        let code = encoder.encode(&op).unwrap();
8033        // VCVT(4) + VMOV(4) = 8
8034        assert_eq!(code.len(), 8);
8035    }
8036
8037    #[test]
8038    fn test_encode_f64_reinterpret_i64_arm32() {
8039        let encoder = ArmEncoder::new_arm32();
8040        let op = ArmOp::F64ReinterpretI64 {
8041            dd: VfpReg::D0,
8042            rmlo: Reg::R0,
8043            rmhi: Reg::R1,
8044        };
8045        let code = encoder.encode(&op).unwrap();
8046        assert_eq!(code.len(), 4); // Single VMOV instruction
8047    }
8048
8049    #[test]
8050    fn test_encode_i64_reinterpret_f64_thumb2() {
8051        let encoder = ArmEncoder::new_thumb2();
8052        let op = ArmOp::I64ReinterpretF64 {
8053            rdlo: Reg::R0,
8054            rdhi: Reg::R1,
8055            dm: VfpReg::D0,
8056        };
8057        let code = encoder.encode(&op).unwrap();
8058        assert_eq!(code.len(), 4);
8059    }
8060
8061    #[test]
8062    fn test_encode_f64_trunc_thumb2() {
8063        let encoder = ArmEncoder::new_thumb2();
8064        let op = ArmOp::F64Trunc {
8065            dd: VfpReg::D0,
8066            dm: VfpReg::D1,
8067        };
8068        let code = encoder.encode(&op).unwrap();
8069        // Two VFP instructions via Thumb encoding
8070        assert_eq!(code.len(), 8);
8071    }
8072
8073    #[test]
8074    fn test_encode_f64_min_arm32() {
8075        let encoder = ArmEncoder::new_arm32();
8076        let op = ArmOp::F64Min {
8077            dd: VfpReg::D0,
8078            dn: VfpReg::D1,
8079            dm: VfpReg::D2,
8080        };
8081        let code = encoder.encode(&op).unwrap();
8082        // VMOV + VCMP + VMRS + conditional VMOV = 16
8083        assert_eq!(code.len(), 16);
8084    }
8085
8086    #[test]
8087    fn test_f64_cp11_encoding() {
8088        // Verify that F64 instructions use coprocessor 11 (0xB), not 10 (0xA)
8089        let encoder = ArmEncoder::new_arm32();
8090
8091        // F64Add
8092        let code = encoder
8093            .encode(&ArmOp::F64Add {
8094                dd: VfpReg::D0,
8095                dn: VfpReg::D0,
8096                dm: VfpReg::D0,
8097            })
8098            .unwrap();
8099        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8100        assert_eq!((instr >> 8) & 0xF, 0xB, "F64 should use cp11");
8101
8102        // F32Add for comparison
8103        let code = encoder
8104            .encode(&ArmOp::F32Add {
8105                sd: VfpReg::S0,
8106                sn: VfpReg::S0,
8107                sm: VfpReg::S0,
8108            })
8109            .unwrap();
8110        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8111        assert_eq!((instr >> 8) & 0xF, 0xA, "F32 should use cp10");
8112    }
8113
8114    #[test]
8115    fn test_dreg_encoding_higher_registers() {
8116        let encoder = ArmEncoder::new_arm32();
8117
8118        // Test with D15 (highest register)
8119        let op = ArmOp::F64Add {
8120            dd: VfpReg::D15,
8121            dn: VfpReg::D14,
8122            dm: VfpReg::D13,
8123        };
8124        let code = encoder.encode(&op).unwrap();
8125        assert_eq!(code.len(), 4);
8126
8127        // Verify the register encoding worked (instruction is valid)
8128        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8129        assert_eq!((instr >> 8) & 0xF, 0xB); // cp11
8130    }
8131
8132    // ========================================================================
8133    // Control flow encoding tests
8134    // ========================================================================
8135
8136    #[test]
8137    fn test_encode_label_emits_no_bytes() {
8138        let encoder = ArmEncoder::new_thumb2();
8139        let op = ArmOp::Label {
8140            name: ".Lblock_end_0".to_string(),
8141        };
8142        let code = encoder.encode(&op).unwrap();
8143        assert!(code.is_empty(), "Label should emit zero bytes");
8144
8145        let encoder32 = ArmEncoder::new_arm32();
8146        let code32 = encoder32.encode(&op).unwrap();
8147        assert!(
8148            code32.is_empty(),
8149            "Label should emit zero bytes in ARM32 too"
8150        );
8151    }
8152
8153    #[test]
8154    fn test_encode_bcc_eq_thumb2() {
8155        use synth_synthesis::Condition;
8156        let encoder = ArmEncoder::new_thumb2();
8157        let op = ArmOp::Bcc {
8158            cond: Condition::EQ,
8159            label: "target".to_string(),
8160        };
8161        let code = encoder.encode(&op).unwrap();
8162        assert_eq!(code.len(), 2); // 16-bit conditional branch
8163
8164        // BEQ with offset 0: 0xD000 in little-endian
8165        assert_eq!(code, vec![0x00, 0xD0]);
8166    }
8167
8168    #[test]
8169    fn test_encode_bcc_ne_thumb2() {
8170        use synth_synthesis::Condition;
8171        let encoder = ArmEncoder::new_thumb2();
8172        let op = ArmOp::Bcc {
8173            cond: Condition::NE,
8174            label: "target".to_string(),
8175        };
8176        let code = encoder.encode(&op).unwrap();
8177        assert_eq!(code.len(), 2);
8178
8179        // BNE with offset 0: 0xD100 in little-endian
8180        assert_eq!(code, vec![0x00, 0xD1]);
8181    }
8182
8183    #[test]
8184    fn test_encode_bcc_arm32() {
8185        use synth_synthesis::Condition;
8186        let encoder = ArmEncoder::new_arm32();
8187        let op = ArmOp::Bcc {
8188            cond: Condition::EQ,
8189            label: "target".to_string(),
8190        };
8191        let code = encoder.encode(&op).unwrap();
8192        assert_eq!(code.len(), 4); // 32-bit ARM instruction
8193
8194        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8195        // BEQ: cond=0x0, opcode=0xA, offset=0
8196        assert_eq!(instr & 0xF0000000, 0x00000000); // EQ condition
8197        assert_eq!(instr & 0x0F000000, 0x0A000000); // Branch opcode
8198    }
8199
8200    #[test]
8201    fn test_encode_udf_thumb2() {
8202        let encoder = ArmEncoder::new_thumb2();
8203        let op = ArmOp::Udf { imm: 0 };
8204        let code = encoder.encode(&op).unwrap();
8205        assert_eq!(code.len(), 2); // 16-bit
8206
8207        // UDF #0: 0xDE00 in little-endian
8208        assert_eq!(code, vec![0x00, 0xDE]);
8209    }
8210
8211    #[test]
8212    fn test_encode_nop_thumb2() {
8213        let encoder = ArmEncoder::new_thumb2();
8214        let op = ArmOp::Nop;
8215        let code = encoder.encode(&op).unwrap();
8216        assert_eq!(code.len(), 2); // 16-bit
8217
8218        // NOP: 0xBF00 in little-endian
8219        assert_eq!(code, vec![0x00, 0xBF]);
8220    }
8221
8222    // =========================================================================
8223    // i64 Thumb-2 encoding tests
8224    // =========================================================================
8225
8226    #[test]
8227    fn test_encode_i64_add_thumb2() {
8228        let encoder = ArmEncoder::new_thumb2();
8229        let op = ArmOp::I64Add {
8230            rdlo: Reg::R0,
8231            rdhi: Reg::R1,
8232            rnlo: Reg::R0,
8233            rnhi: Reg::R1,
8234            rmlo: Reg::R2,
8235            rmhi: Reg::R3,
8236        };
8237        let code = encoder.encode(&op).unwrap();
8238        // Should emit ADDS (2 bytes) + ADC.W (4 bytes) = 6 bytes
8239        assert_eq!(code.len(), 6, "I64Add should be 6 bytes (ADDS + ADC.W)");
8240    }
8241
8242    #[test]
8243    fn test_encode_i64_sub_thumb2() {
8244        let encoder = ArmEncoder::new_thumb2();
8245        let op = ArmOp::I64Sub {
8246            rdlo: Reg::R0,
8247            rdhi: Reg::R1,
8248            rnlo: Reg::R0,
8249            rnhi: Reg::R1,
8250            rmlo: Reg::R2,
8251            rmhi: Reg::R3,
8252        };
8253        let code = encoder.encode(&op).unwrap();
8254        // Should emit SUBS (2 bytes) + SBC.W (4 bytes) = 6 bytes
8255        assert_eq!(code.len(), 6, "I64Sub should be 6 bytes (SUBS + SBC.W)");
8256    }
8257
8258    #[test]
8259    fn test_encode_i64_and_thumb2() {
8260        let encoder = ArmEncoder::new_thumb2();
8261        let op = ArmOp::I64And {
8262            rdlo: Reg::R0,
8263            rdhi: Reg::R1,
8264            rnlo: Reg::R0,
8265            rnhi: Reg::R1,
8266            rmlo: Reg::R2,
8267            rmhi: Reg::R3,
8268        };
8269        let code = encoder.encode(&op).unwrap();
8270        // AND.W (4 bytes) + AND.W (4 bytes) = 8 bytes
8271        assert!(code.len() >= 4, "I64And should emit at least 4 bytes");
8272    }
8273
8274    #[test]
8275    fn test_encode_i64_or_thumb2() {
8276        let encoder = ArmEncoder::new_thumb2();
8277        let op = ArmOp::I64Or {
8278            rdlo: Reg::R0,
8279            rdhi: Reg::R1,
8280            rnlo: Reg::R0,
8281            rnhi: Reg::R1,
8282            rmlo: Reg::R2,
8283            rmhi: Reg::R3,
8284        };
8285        let code = encoder.encode(&op).unwrap();
8286        assert!(code.len() >= 4, "I64Or should emit at least 4 bytes");
8287    }
8288
8289    #[test]
8290    fn test_encode_i64_xor_thumb2() {
8291        let encoder = ArmEncoder::new_thumb2();
8292        let op = ArmOp::I64Xor {
8293            rdlo: Reg::R0,
8294            rdhi: Reg::R1,
8295            rnlo: Reg::R0,
8296            rnhi: Reg::R1,
8297            rmlo: Reg::R2,
8298            rmhi: Reg::R3,
8299        };
8300        let code = encoder.encode(&op).unwrap();
8301        assert!(code.len() >= 4, "I64Xor should emit at least 4 bytes");
8302    }
8303
8304    #[test]
8305    fn test_encode_i64_const_small_thumb2() {
8306        let encoder = ArmEncoder::new_thumb2();
8307        // Small constant: only needs MOVW for each half
8308        let op = ArmOp::I64Const {
8309            rdlo: Reg::R0,
8310            rdhi: Reg::R1,
8311            value: 42,
8312        };
8313        let code = encoder.encode(&op).unwrap();
8314        // MOVW R0, #42 (4 bytes) + MOVW R1, #0 (4 bytes) = 8 bytes minimum
8315        assert!(code.len() >= 8, "I64Const should emit at least 8 bytes");
8316    }
8317
8318    #[test]
8319    fn test_encode_i64_const_large_thumb2() {
8320        let encoder = ArmEncoder::new_thumb2();
8321        // Large constant: needs MOVW+MOVT for each half
8322        let op = ArmOp::I64Const {
8323            rdlo: Reg::R0,
8324            rdhi: Reg::R1,
8325            value: 0x1234_5678_9ABC_DEF0_u64 as i64,
8326        };
8327        let code = encoder.encode(&op).unwrap();
8328        // MOVW + MOVT for lo (8 bytes) + MOVW + MOVT for hi (8 bytes) = 16 bytes
8329        assert_eq!(
8330            code.len(),
8331            16,
8332            "I64Const with large value should be 16 bytes"
8333        );
8334    }
8335
8336    #[test]
8337    fn test_encode_i64_extend_i32_s_thumb2() {
8338        let encoder = ArmEncoder::new_thumb2();
8339        let op = ArmOp::I64ExtendI32S {
8340            rdlo: Reg::R0,
8341            rdhi: Reg::R1,
8342            rn: Reg::R0,
8343        };
8344        let code = encoder.encode(&op).unwrap();
8345        // When rdlo == rn, only ASR (4 bytes) is emitted
8346        assert_eq!(
8347            code.len(),
8348            4,
8349            "I64ExtendI32S (same reg) should be 4 bytes (ASR only)"
8350        );
8351    }
8352
8353    #[test]
8354    fn test_encode_i64_extend_i32_s_diff_reg_thumb2() {
8355        let encoder = ArmEncoder::new_thumb2();
8356        let op = ArmOp::I64ExtendI32S {
8357            rdlo: Reg::R0,
8358            rdhi: Reg::R1,
8359            rn: Reg::R2,
8360        };
8361        let code = encoder.encode(&op).unwrap();
8362        // MOV rdlo, rn (2 bytes for low regs) + ASR rdhi, rdlo, #31 (4 bytes) = 6 bytes
8363        assert!(
8364            code.len() >= 6,
8365            "I64ExtendI32S (diff reg) should be at least 6 bytes"
8366        );
8367    }
8368
8369    #[test]
8370    fn test_encode_i64_extend_i32_u_thumb2() {
8371        let encoder = ArmEncoder::new_thumb2();
8372        let op = ArmOp::I64ExtendI32U {
8373            rdlo: Reg::R0,
8374            rdhi: Reg::R1,
8375            rn: Reg::R0,
8376        };
8377        let code = encoder.encode(&op).unwrap();
8378        // When rdlo == rn, only MOV rdhi, #0 (2 bytes) is emitted
8379        assert_eq!(
8380            code.len(),
8381            2,
8382            "I64ExtendI32U (same reg) should be 2 bytes (MOV #0 only)"
8383        );
8384    }
8385
8386    #[test]
8387    fn test_encode_i32_wrap_i64_nop_thumb2() {
8388        let encoder = ArmEncoder::new_thumb2();
8389        // When rd == rnlo, should be a NOP
8390        let op = ArmOp::I32WrapI64 {
8391            rd: Reg::R0,
8392            rnlo: Reg::R0,
8393        };
8394        let code = encoder.encode(&op).unwrap();
8395        assert_eq!(code.len(), 2, "I32WrapI64 same reg should be NOP (2 bytes)");
8396        assert_eq!(code, vec![0x00, 0xBF]); // NOP
8397    }
8398
8399    #[test]
8400    fn test_encode_i32_wrap_i64_diff_reg_thumb2() {
8401        let encoder = ArmEncoder::new_thumb2();
8402        let op = ArmOp::I32WrapI64 {
8403            rd: Reg::R2,
8404            rnlo: Reg::R0,
8405        };
8406        let code = encoder.encode(&op).unwrap();
8407        // MOV R2, R0 (2 or 4 bytes)
8408        assert!(
8409            code.len() >= 2,
8410            "I32WrapI64 diff reg should emit at least 2 bytes"
8411        );
8412    }
8413
8414    #[test]
8415    fn test_encode_i64_eqz_thumb2() {
8416        let encoder = ArmEncoder::new_thumb2();
8417        let op = ArmOp::I64Eqz {
8418            rd: Reg::R0,
8419            rnlo: Reg::R0,
8420            rnhi: Reg::R1,
8421        };
8422        let code = encoder.encode(&op).unwrap();
8423        // Delegates to I64SetCondZ which is already encoded
8424        assert!(
8425            code.len() >= 6,
8426            "I64Eqz should emit at least 6 bytes for ORR+ITE+MOV+MOV"
8427        );
8428    }
8429
8430    #[test]
8431    fn test_encode_i64_eq_thumb2() {
8432        let encoder = ArmEncoder::new_thumb2();
8433        let op = ArmOp::I64Eq {
8434            rd: Reg::R0,
8435            rnlo: Reg::R0,
8436            rnhi: Reg::R1,
8437            rmlo: Reg::R2,
8438            rmhi: Reg::R3,
8439        };
8440        let code = encoder.encode(&op).unwrap();
8441        // Delegates to I64SetCond EQ: CMP lo + IT EQ + CMPEQ hi + ITE EQ + MOV 1 + MOV 0
8442        assert!(code.len() >= 10, "I64Eq should emit at least 10 bytes");
8443    }
8444
8445    #[test]
8446    fn test_encode_i64_ldr_thumb2() {
8447        let encoder = ArmEncoder::new_thumb2();
8448        let op = ArmOp::I64Ldr {
8449            rdlo: Reg::R0,
8450            rdhi: Reg::R1,
8451            addr: MemAddr::imm(Reg::SP, 0),
8452        };
8453        let code = encoder.encode(&op).unwrap();
8454        // Two LDR instructions (lo at offset, hi at offset+4)
8455        assert!(code.len() >= 4, "I64Ldr should emit at least 4 bytes");
8456    }
8457
8458    #[test]
8459    fn test_encode_i64_str_thumb2() {
8460        let encoder = ArmEncoder::new_thumb2();
8461        let op = ArmOp::I64Str {
8462            rdlo: Reg::R0,
8463            rdhi: Reg::R1,
8464            addr: MemAddr::imm(Reg::SP, 0),
8465        };
8466        let code = encoder.encode(&op).unwrap();
8467        // Two STR instructions (lo at offset, hi at offset+4)
8468        assert!(code.len() >= 4, "I64Str should emit at least 4 bytes");
8469    }
8470
8471    #[test]
8472    fn test_encode_i64_all_comparisons_thumb2() {
8473        let encoder = ArmEncoder::new_thumb2();
8474
8475        let ops = vec![
8476            ArmOp::I64Ne {
8477                rd: Reg::R0,
8478                rnlo: Reg::R0,
8479                rnhi: Reg::R1,
8480                rmlo: Reg::R2,
8481                rmhi: Reg::R3,
8482            },
8483            ArmOp::I64LtS {
8484                rd: Reg::R0,
8485                rnlo: Reg::R0,
8486                rnhi: Reg::R1,
8487                rmlo: Reg::R2,
8488                rmhi: Reg::R3,
8489            },
8490            ArmOp::I64LtU {
8491                rd: Reg::R0,
8492                rnlo: Reg::R0,
8493                rnhi: Reg::R1,
8494                rmlo: Reg::R2,
8495                rmhi: Reg::R3,
8496            },
8497            ArmOp::I64LeS {
8498                rd: Reg::R0,
8499                rnlo: Reg::R0,
8500                rnhi: Reg::R1,
8501                rmlo: Reg::R2,
8502                rmhi: Reg::R3,
8503            },
8504            ArmOp::I64LeU {
8505                rd: Reg::R0,
8506                rnlo: Reg::R0,
8507                rnhi: Reg::R1,
8508                rmlo: Reg::R2,
8509                rmhi: Reg::R3,
8510            },
8511            ArmOp::I64GtS {
8512                rd: Reg::R0,
8513                rnlo: Reg::R0,
8514                rnhi: Reg::R1,
8515                rmlo: Reg::R2,
8516                rmhi: Reg::R3,
8517            },
8518            ArmOp::I64GtU {
8519                rd: Reg::R0,
8520                rnlo: Reg::R0,
8521                rnhi: Reg::R1,
8522                rmlo: Reg::R2,
8523                rmhi: Reg::R3,
8524            },
8525            ArmOp::I64GeS {
8526                rd: Reg::R0,
8527                rnlo: Reg::R0,
8528                rnhi: Reg::R1,
8529                rmlo: Reg::R2,
8530                rmhi: Reg::R3,
8531            },
8532            ArmOp::I64GeU {
8533                rd: Reg::R0,
8534                rnlo: Reg::R0,
8535                rnhi: Reg::R1,
8536                rmlo: Reg::R2,
8537                rmhi: Reg::R3,
8538            },
8539        ];
8540
8541        for op in &ops {
8542            let code = encoder.encode(op).unwrap();
8543            assert!(
8544                code.len() >= 8,
8545                "i64 comparison {:?} should emit at least 8 bytes, got {}",
8546                op,
8547                code.len()
8548            );
8549        }
8550    }
8551
8552    #[test]
8553    fn test_encode_i64_const_zero_thumb2() {
8554        let encoder = ArmEncoder::new_thumb2();
8555        let op = ArmOp::I64Const {
8556            rdlo: Reg::R0,
8557            rdhi: Reg::R1,
8558            value: 0,
8559        };
8560        let code = encoder.encode(&op).unwrap();
8561        // MOVW R0, #0 (4 bytes) + MOVW R1, #0 (4 bytes) = 8 bytes
8562        assert_eq!(code.len(), 8, "I64Const(0) should be 8 bytes");
8563    }
8564
8565    #[test]
8566    fn test_encode_i64_const_negative_one_thumb2() {
8567        let encoder = ArmEncoder::new_thumb2();
8568        let op = ArmOp::I64Const {
8569            rdlo: Reg::R0,
8570            rdhi: Reg::R1,
8571            value: -1, // 0xFFFF_FFFF_FFFF_FFFF
8572        };
8573        let code = encoder.encode(&op).unwrap();
8574        // MOVW + MOVT for lo (8 bytes) + MOVW + MOVT for hi (8 bytes) = 16 bytes
8575        assert_eq!(code.len(), 16, "I64Const(-1) should be 16 bytes");
8576    }
8577
8578    // =========================================================================
8579    // Sub-word load/store encoding tests
8580    // =========================================================================
8581
8582    #[test]
8583    fn test_encode_ldrb_arm32() {
8584        let encoder = ArmEncoder::new_arm32();
8585        let op = ArmOp::Ldrb {
8586            rd: Reg::R0,
8587            addr: MemAddr::imm(Reg::R1, 4),
8588        };
8589        let code = encoder.encode(&op).unwrap();
8590        assert_eq!(code.len(), 4, "ARM32 LDRB should be 4 bytes");
8591        // LDRB R0, [R1, #4] = 0xE5D10004
8592        let encoded = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8593        assert_eq!(encoded, 0xE5D10004, "Should encode LDRB R0, [R1, #4]");
8594    }
8595
8596    #[test]
8597    fn test_encode_strb_arm32() {
8598        let encoder = ArmEncoder::new_arm32();
8599        let op = ArmOp::Strb {
8600            rd: Reg::R0,
8601            addr: MemAddr::imm(Reg::R1, 0),
8602        };
8603        let code = encoder.encode(&op).unwrap();
8604        assert_eq!(code.len(), 4, "ARM32 STRB should be 4 bytes");
8605        // STRB R0, [R1, #0] = 0xE5C10000
8606        let encoded = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8607        assert_eq!(encoded, 0xE5C10000, "Should encode STRB R0, [R1, #0]");
8608    }
8609
8610    #[test]
8611    fn test_encode_ldrh_arm32() {
8612        let encoder = ArmEncoder::new_arm32();
8613        let op = ArmOp::Ldrh {
8614            rd: Reg::R0,
8615            addr: MemAddr::imm(Reg::R1, 2),
8616        };
8617        let code = encoder.encode(&op).unwrap();
8618        assert_eq!(code.len(), 4, "ARM32 LDRH should be 4 bytes");
8619    }
8620
8621    #[test]
8622    fn test_encode_strh_arm32() {
8623        let encoder = ArmEncoder::new_arm32();
8624        let op = ArmOp::Strh {
8625            rd: Reg::R0,
8626            addr: MemAddr::imm(Reg::R1, 0),
8627        };
8628        let code = encoder.encode(&op).unwrap();
8629        assert_eq!(code.len(), 4, "ARM32 STRH should be 4 bytes");
8630    }
8631
8632    #[test]
8633    fn test_encode_ldrsb_arm32() {
8634        let encoder = ArmEncoder::new_arm32();
8635        let op = ArmOp::Ldrsb {
8636            rd: Reg::R0,
8637            addr: MemAddr::imm(Reg::R1, 0),
8638        };
8639        let code = encoder.encode(&op).unwrap();
8640        assert_eq!(code.len(), 4, "ARM32 LDRSB should be 4 bytes");
8641    }
8642
8643    #[test]
8644    fn test_encode_ldrsh_arm32() {
8645        let encoder = ArmEncoder::new_arm32();
8646        let op = ArmOp::Ldrsh {
8647            rd: Reg::R0,
8648            addr: MemAddr::imm(Reg::R1, 0),
8649        };
8650        let code = encoder.encode(&op).unwrap();
8651        assert_eq!(code.len(), 4, "ARM32 LDRSH should be 4 bytes");
8652    }
8653
8654    #[test]
8655    fn test_encode_ldrb_thumb2_16bit() {
8656        let encoder = ArmEncoder::new_thumb2();
8657        let op = ArmOp::Ldrb {
8658            rd: Reg::R0,
8659            addr: MemAddr::imm(Reg::R1, 4),
8660        };
8661        let code = encoder.encode(&op).unwrap();
8662        // Low registers + small offset -> 16-bit encoding
8663        assert_eq!(
8664            code.len(),
8665            2,
8666            "Thumb-2 LDRB with small offset should be 16-bit"
8667        );
8668    }
8669
8670    #[test]
8671    fn test_encode_ldrb_thumb2_32bit() {
8672        let encoder = ArmEncoder::new_thumb2();
8673        let op = ArmOp::Ldrb {
8674            rd: Reg::R0,
8675            addr: MemAddr::imm(Reg::R1, 100), // offset > 31 needs 32-bit
8676        };
8677        let code = encoder.encode(&op).unwrap();
8678        assert_eq!(
8679            code.len(),
8680            4,
8681            "Thumb-2 LDRB with large offset should be 32-bit"
8682        );
8683    }
8684
8685    #[test]
8686    fn test_encode_strb_thumb2_16bit() {
8687        let encoder = ArmEncoder::new_thumb2();
8688        let op = ArmOp::Strb {
8689            rd: Reg::R0,
8690            addr: MemAddr::imm(Reg::R1, 10),
8691        };
8692        let code = encoder.encode(&op).unwrap();
8693        assert_eq!(
8694            code.len(),
8695            2,
8696            "Thumb-2 STRB with small offset should be 16-bit"
8697        );
8698    }
8699
8700    #[test]
8701    fn test_encode_ldrh_thumb2_16bit() {
8702        let encoder = ArmEncoder::new_thumb2();
8703        let op = ArmOp::Ldrh {
8704            rd: Reg::R0,
8705            addr: MemAddr::imm(Reg::R1, 4), // offset aligned to 2, <= 62
8706        };
8707        let code = encoder.encode(&op).unwrap();
8708        assert_eq!(
8709            code.len(),
8710            2,
8711            "Thumb-2 LDRH with small aligned offset should be 16-bit"
8712        );
8713    }
8714
8715    #[test]
8716    fn test_encode_strh_thumb2_16bit() {
8717        let encoder = ArmEncoder::new_thumb2();
8718        let op = ArmOp::Strh {
8719            rd: Reg::R0,
8720            addr: MemAddr::imm(Reg::R1, 4),
8721        };
8722        let code = encoder.encode(&op).unwrap();
8723        assert_eq!(
8724            code.len(),
8725            2,
8726            "Thumb-2 STRH with small aligned offset should be 16-bit"
8727        );
8728    }
8729
8730    #[test]
8731    fn test_encode_ldrsb_thumb2() {
8732        let encoder = ArmEncoder::new_thumb2();
8733        let op = ArmOp::Ldrsb {
8734            rd: Reg::R0,
8735            addr: MemAddr::imm(Reg::R1, 0),
8736        };
8737        let code = encoder.encode(&op).unwrap();
8738        // LDRSB has no 16-bit immediate form, always 32-bit
8739        assert_eq!(code.len(), 4, "Thumb-2 LDRSB should be 32-bit");
8740    }
8741
8742    #[test]
8743    fn test_encode_ldrsh_thumb2() {
8744        let encoder = ArmEncoder::new_thumb2();
8745        let op = ArmOp::Ldrsh {
8746            rd: Reg::R0,
8747            addr: MemAddr::imm(Reg::R1, 0),
8748        };
8749        let code = encoder.encode(&op).unwrap();
8750        assert_eq!(code.len(), 4, "Thumb-2 LDRSH should be 32-bit");
8751    }
8752
8753    #[test]
8754    fn test_encode_memory_size_thumb2() {
8755        let encoder = ArmEncoder::new_thumb2();
8756        let op = ArmOp::MemorySize { rd: Reg::R0 };
8757        let code = encoder.encode(&op).unwrap();
8758        // R0 and R10 are not both low registers, so this needs careful handling
8759        assert!(!code.is_empty(), "MemorySize should produce code");
8760    }
8761
8762    #[test]
8763    fn test_encode_memory_grow_thumb2() {
8764        let encoder = ArmEncoder::new_thumb2();
8765        let op = ArmOp::MemoryGrow {
8766            rd: Reg::R0,
8767            rn: Reg::R0,
8768        };
8769        let code = encoder.encode(&op).unwrap();
8770        assert_eq!(code.len(), 4, "MemoryGrow (MVN) should be 32-bit Thumb-2");
8771    }
8772
8773    #[test]
8774    fn test_encode_subword_reg_offset_thumb2() {
8775        let encoder = ArmEncoder::new_thumb2();
8776
8777        // LDRB with register offset
8778        let op = ArmOp::Ldrb {
8779            rd: Reg::R0,
8780            addr: MemAddr::reg(Reg::R1, Reg::R2),
8781        };
8782        let code = encoder.encode(&op).unwrap();
8783        assert_eq!(
8784            code.len(),
8785            4,
8786            "Thumb-2 LDRB with reg offset should be 32-bit"
8787        );
8788
8789        // STRB with register offset
8790        let op = ArmOp::Strb {
8791            rd: Reg::R0,
8792            addr: MemAddr::reg(Reg::R1, Reg::R2),
8793        };
8794        let code = encoder.encode(&op).unwrap();
8795        assert_eq!(
8796            code.len(),
8797            4,
8798            "Thumb-2 STRB with reg offset should be 32-bit"
8799        );
8800
8801        // LDRH with register offset
8802        let op = ArmOp::Ldrh {
8803            rd: Reg::R0,
8804            addr: MemAddr::reg(Reg::R1, Reg::R2),
8805        };
8806        let code = encoder.encode(&op).unwrap();
8807        assert_eq!(
8808            code.len(),
8809            4,
8810            "Thumb-2 LDRH with reg offset should be 32-bit"
8811        );
8812
8813        // STRH with register offset
8814        let op = ArmOp::Strh {
8815            rd: Reg::R0,
8816            addr: MemAddr::reg(Reg::R1, Reg::R2),
8817        };
8818        let code = encoder.encode(&op).unwrap();
8819        assert_eq!(
8820            code.len(),
8821            4,
8822            "Thumb-2 STRH with reg offset should be 32-bit"
8823        );
8824    }
8825
8826    #[test]
8827    fn test_encode_subword_reg_imm_offset_thumb2() {
8828        let encoder = ArmEncoder::new_thumb2();
8829
8830        // LDRB with both register and immediate offset
8831        let op = ArmOp::Ldrb {
8832            rd: Reg::R0,
8833            addr: MemAddr::reg_imm(Reg::R1, Reg::R2, 4),
8834        };
8835        let code = encoder.encode(&op).unwrap();
8836        // ADD R12, R2, #4 (4 bytes) + LDRB R0, [R1, R12] (4 bytes) = 8 bytes
8837        assert_eq!(
8838            code.len(),
8839            8,
8840            "Thumb-2 LDRB with reg+imm offset should be 8 bytes"
8841        );
8842    }
8843
8844    // ========================================================================
8845    // Helium MVE encoding tests
8846    // ========================================================================
8847
8848    #[test]
8849    fn test_encode_mve_addi32_thumb2() {
8850        let encoder = ArmEncoder::new_thumb2();
8851        let op = ArmOp::MveAddI {
8852            qd: QReg::Q0,
8853            qn: QReg::Q1,
8854            qm: QReg::Q2,
8855            size: MveSize::S32,
8856        };
8857        let code = encoder.encode(&op).unwrap();
8858        assert_eq!(
8859            code.len(),
8860            4,
8861            "MVE VADD.I32 should be 4 bytes (Thumb-2 32-bit)"
8862        );
8863    }
8864
8865    #[test]
8866    fn test_encode_mve_subi16_thumb2() {
8867        let encoder = ArmEncoder::new_thumb2();
8868        let op = ArmOp::MveSubI {
8869            qd: QReg::Q0,
8870            qn: QReg::Q1,
8871            qm: QReg::Q2,
8872            size: MveSize::S16,
8873        };
8874        let code = encoder.encode(&op).unwrap();
8875        assert_eq!(code.len(), 4, "MVE VSUB.I16 should be 4 bytes");
8876    }
8877
8878    #[test]
8879    fn test_encode_mve_muli8_thumb2() {
8880        let encoder = ArmEncoder::new_thumb2();
8881        let op = ArmOp::MveMulI {
8882            qd: QReg::Q0,
8883            qn: QReg::Q1,
8884            qm: QReg::Q2,
8885            size: MveSize::S8,
8886        };
8887        let code = encoder.encode(&op).unwrap();
8888        assert_eq!(code.len(), 4, "MVE VMUL.I8 should be 4 bytes");
8889    }
8890
8891    #[test]
8892    fn test_encode_mve_bitwise_thumb2() {
8893        let encoder = ArmEncoder::new_thumb2();
8894
8895        let ops = vec![
8896            ArmOp::MveAnd {
8897                qd: QReg::Q0,
8898                qn: QReg::Q1,
8899                qm: QReg::Q2,
8900            },
8901            ArmOp::MveOrr {
8902                qd: QReg::Q0,
8903                qn: QReg::Q1,
8904                qm: QReg::Q2,
8905            },
8906            ArmOp::MveEor {
8907                qd: QReg::Q0,
8908                qn: QReg::Q1,
8909                qm: QReg::Q2,
8910            },
8911            ArmOp::MveBic {
8912                qd: QReg::Q0,
8913                qn: QReg::Q1,
8914                qm: QReg::Q2,
8915            },
8916        ];
8917        for op in ops {
8918            let code = encoder.encode(&op).unwrap();
8919            assert_eq!(code.len(), 4, "MVE bitwise op should be 4 bytes");
8920        }
8921    }
8922
8923    #[test]
8924    fn test_encode_mve_mvn_thumb2() {
8925        let encoder = ArmEncoder::new_thumb2();
8926        let op = ArmOp::MveMvn {
8927            qd: QReg::Q0,
8928            qm: QReg::Q1,
8929        };
8930        let code = encoder.encode(&op).unwrap();
8931        assert_eq!(code.len(), 4, "MVE VMVN should be 4 bytes");
8932    }
8933
8934    #[test]
8935    fn test_encode_mve_load_store_thumb2() {
8936        let encoder = ArmEncoder::new_thumb2();
8937
8938        let load = ArmOp::MveLoad {
8939            qd: QReg::Q0,
8940            addr: MemAddr::imm(Reg::R0, 16),
8941        };
8942        let code = encoder.encode(&load).unwrap();
8943        assert_eq!(code.len(), 4, "MVE VLDRW.32 should be 4 bytes");
8944
8945        let store = ArmOp::MveStore {
8946            qd: QReg::Q1,
8947            addr: MemAddr::imm(Reg::R1, 0),
8948        };
8949        let code = encoder.encode(&store).unwrap();
8950        assert_eq!(code.len(), 4, "MVE VSTRW.32 should be 4 bytes");
8951    }
8952
8953    #[test]
8954    fn test_encode_mve_const_thumb2() {
8955        let encoder = ArmEncoder::new_thumb2();
8956        let op = ArmOp::MveConst {
8957            qd: QReg::Q0,
8958            bytes: [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0],
8959        };
8960        let code = encoder.encode(&op).unwrap();
8961        // Should be 4 words of (MOVW R12 + VMOV Sn) = 4 * (4+4) = 32 bytes min
8962        // Some words with hi16=0 skip MOVT, so length varies
8963        assert!(
8964            code.len() >= 24,
8965            "MVE const should produce multiple instructions"
8966        );
8967    }
8968
8969    #[test]
8970    fn test_encode_mve_dup_thumb2() {
8971        let encoder = ArmEncoder::new_thumb2();
8972        let op = ArmOp::MveDup {
8973            qd: QReg::Q0,
8974            rn: Reg::R0,
8975            size: MveSize::S32,
8976        };
8977        let code = encoder.encode(&op).unwrap();
8978        assert_eq!(code.len(), 4, "MVE VDUP.32 should be 4 bytes");
8979    }
8980
8981    #[test]
8982    fn test_encode_mve_extract_lane_thumb2() {
8983        let encoder = ArmEncoder::new_thumb2();
8984        let op = ArmOp::MveExtractLane {
8985            rd: Reg::R0,
8986            qn: QReg::Q1,
8987            lane: 2,
8988            size: MveSize::S32,
8989        };
8990        let code = encoder.encode(&op).unwrap();
8991        assert_eq!(code.len(), 4, "MVE extract lane should be 4 bytes");
8992    }
8993
8994    #[test]
8995    fn test_encode_mve_insert_lane_thumb2() {
8996        let encoder = ArmEncoder::new_thumb2();
8997        let op = ArmOp::MveInsertLane {
8998            qd: QReg::Q0,
8999            rn: Reg::R1,
9000            lane: 3,
9001            size: MveSize::S32,
9002        };
9003        let code = encoder.encode(&op).unwrap();
9004        assert_eq!(code.len(), 4, "MVE insert lane should be 4 bytes");
9005    }
9006
9007    #[test]
9008    fn test_encode_mve_addf32_thumb2() {
9009        let encoder = ArmEncoder::new_thumb2();
9010        let op = ArmOp::MveAddF32 {
9011            qd: QReg::Q0,
9012            qn: QReg::Q1,
9013            qm: QReg::Q2,
9014        };
9015        let code = encoder.encode(&op).unwrap();
9016        assert_eq!(code.len(), 4, "MVE VADD.F32 should be 4 bytes");
9017    }
9018
9019    #[test]
9020    fn test_encode_mve_divf32_thumb2() {
9021        let encoder = ArmEncoder::new_thumb2();
9022        let op = ArmOp::MveDivF32 {
9023            qd: QReg::Q0,
9024            qn: QReg::Q1,
9025            qm: QReg::Q2,
9026        };
9027        let code = encoder.encode(&op).unwrap();
9028        // Lane-wise: 4 x VDIV.F32 = 4 x 4 = 16 bytes
9029        assert_eq!(
9030            code.len(),
9031            16,
9032            "MVE VDIV.F32 (lane-wise) should be 16 bytes"
9033        );
9034    }
9035
9036    #[test]
9037    fn test_encode_mve_sqrtf32_thumb2() {
9038        let encoder = ArmEncoder::new_thumb2();
9039        let op = ArmOp::MveSqrtF32 {
9040            qd: QReg::Q0,
9041            qm: QReg::Q1,
9042        };
9043        let code = encoder.encode(&op).unwrap();
9044        // Lane-wise: 4 x VSQRT.F32 = 4 x 4 = 16 bytes
9045        assert_eq!(
9046            code.len(),
9047            16,
9048            "MVE VSQRT.F32 (lane-wise) should be 16 bytes"
9049        );
9050    }
9051
9052    #[test]
9053    fn test_encode_mve_negf32_thumb2() {
9054        let encoder = ArmEncoder::new_thumb2();
9055        let op = ArmOp::MveNegF32 {
9056            qd: QReg::Q0,
9057            qm: QReg::Q1,
9058        };
9059        let code = encoder.encode(&op).unwrap();
9060        assert_eq!(code.len(), 4, "MVE VNEG.F32 should be 4 bytes");
9061    }
9062
9063    #[test]
9064    fn test_encode_mve_absf32_thumb2() {
9065        let encoder = ArmEncoder::new_thumb2();
9066        let op = ArmOp::MveAbsF32 {
9067            qd: QReg::Q0,
9068            qm: QReg::Q1,
9069        };
9070        let code = encoder.encode(&op).unwrap();
9071        assert_eq!(code.len(), 4, "MVE VABS.F32 should be 4 bytes");
9072    }
9073
9074    /// VCR-RA-001 / immediate-folding precondition: pins the Thumb-2 `AND`
9075    /// immediate encoding for the byte range and documents its bound.
9076    ///
9077    /// The `And { Operand2::Imm }` encoder packs the low 12 bits straight into
9078    /// the `i:imm3:imm8` field WITHOUT applying ThumbExpandImm (the modified-
9079    /// immediate expansion). For `imm <= 0xFF` (e.g. gale's int8 clamps
9080    /// `#0x7e` / `#0x7f`) that is correct — `i:imm3 = 0000` means "imm8
9081    /// zero-extended". So `and r2, r0, #0x7e` encodes to the canonical
9082    /// `00 f0 7e 02`. For `imm >= 0x100` the field would need a true
9083    /// ThumbExpandImm pattern (rotation / replication), which is NOT
9084    /// implemented here — so **immediate folding must gate on `imm <= 0xFF`**
9085    /// until the encoder is hardened to ThumbExpandImm/Ok-or-Err (the
9086    /// "encoder must be Ok-or-Err, never silently wrong" principle, #180/#185).
9087    /// This bound covers the measured `flat_flight` waste (#209).
9088    #[test]
9089    fn and_immediate_encodes_correctly_in_byte_range_documents_fold_bound() {
9090        let encoder = ArmEncoder::new_thumb2();
9091        let op = ArmOp::And {
9092            rd: Reg::R2,
9093            rn: Reg::R0,
9094            op2: Operand2::Imm(0x7e),
9095        };
9096        let code = encoder.encode(&op).unwrap();
9097        assert_eq!(
9098            code,
9099            vec![0x00, 0xf0, 0x7e, 0x02],
9100            "and r2, r0, #0x7e must encode to the canonical AND.W T1 (imm8=0x7e)"
9101        );
9102    }
9103
9104    /// #255: the shared ThumbExpandImm reverse-encoder underpinning the
9105    /// data-processing immediate fix. Encodable modified immediates round-trip to
9106    /// the expected `i:imm3:imm8` field; a genuinely non-modified value is `None`
9107    /// (caller must materialize into a register). Note `1000 = 0xFA ror 30` *is*
9108    /// representable (field 0xF7A) — the old encoder mis-encoded it (raw 0x3E8);
9109    /// this encodes it correctly.
9110    #[test]
9111    fn try_thumb_expand_imm_encodes_modified_immediates() {
9112        assert_eq!(try_thumb_expand_imm(0x7e), Some(0x07e)); // zero-extended byte
9113        assert_eq!(try_thumb_expand_imm(0xff), Some(0x0ff));
9114        assert_eq!(try_thumb_expand_imm(0x0001_0001), Some(0x101)); // 0x00XY00XY
9115        assert_eq!(try_thumb_expand_imm(0xff00_ff00), Some(0x2ff)); // 0xXY00XY00
9116        assert_eq!(try_thumb_expand_imm(0xffff_ffff), Some(0x3ff)); // 0xXYXYXYXY
9117        assert_eq!(try_thumb_expand_imm(0x100), Some(0xf80)); // 0x80 ror 31
9118        assert_eq!(try_thumb_expand_imm(0x8000_0000), Some(0x400)); // 0x80 ror 8
9119        assert_eq!(try_thumb_expand_imm(1000), Some(0xf7a)); // 0xFA ror 30
9120        // Genuinely unrepresentable (bits too far apart for an 8-bit window).
9121        assert_eq!(try_thumb_expand_imm(0x101), None);
9122        assert_eq!(try_thumb_expand_imm(0x12345), None);
9123    }
9124
9125    /// #255: CMP/ADDS/SUBS encode any valid modified immediate correctly, and
9126    /// ERROR (not silently mis-encode) on a genuinely unrepresentable one,
9127    /// forcing the selector to materialize into a register — closing the
9128    /// silent-miscompile class of #251/#253.
9129    #[test]
9130    fn cmp_adds_subs_immediate_error_on_non_modified_imm() {
9131        let encoder = ArmEncoder::new_thumb2();
9132        // cmp r0, #0xff → valid → Ok; cmp r0, #1000 → valid (0xFA ror 30) → Ok.
9133        assert!(encoder.encode_thumb32_cmp_imm(&Reg::R0, 0xff).is_ok());
9134        assert!(encoder.encode_thumb32_cmp_imm(&Reg::R0, 1000).is_ok());
9135        // cmp r0, #0x101 → NOT a modified immediate → Err (materialize-reg).
9136        assert!(
9137            encoder.encode_thumb32_cmp_imm(&Reg::R0, 0x101).is_err(),
9138            "cmp #0x101 must error, not compare the wrong constant"
9139        );
9140        assert!(
9141            encoder
9142                .encode_thumb32_adds(&Reg::R0, &Reg::R0, 0x101)
9143                .is_err()
9144        );
9145        assert!(
9146            encoder
9147                .encode_thumb32_subs(&Reg::R0, &Reg::R0, 0x101)
9148                .is_err()
9149        );
9150        // ...but a valid modified immediate still encodes.
9151        assert!(
9152            encoder
9153                .encode_thumb32_adds(&Reg::R0, &Reg::R0, 0x80)
9154                .is_ok()
9155        );
9156    }
9157
9158    /// Latent miscompile fix: ADD/SUB with a >0xFF immediate (e.g.
9159    /// `add sp, sp, #frame` for a >=256-byte frame) used ADD.W (T3), whose
9160    /// `i:imm3:imm8` is a ThumbExpandImm modified immediate — so `#256` silently
9161    /// encoded as `#0` (stack corruption). Use ADDW/SUBW (T4), a PLAIN 12-bit
9162    /// immediate, for 0x100..=0xFFF; keep T3 for <=0xFF (bit-identical); error
9163    /// beyond 4095.
9164    #[test]
9165    fn add_sub_large_immediate_use_addw_subw_not_misencoded() {
9166        let encoder = ArmEncoder::new_thumb2();
9167        // add sp, sp, #256  →  ADDW (T4) SP, SP, #256  =  0d f2 00 1d
9168        assert_eq!(
9169            encoder
9170                .encode(&ArmOp::Add {
9171                    rd: Reg::SP,
9172                    rn: Reg::SP,
9173                    op2: Operand2::Imm(256),
9174                })
9175                .unwrap(),
9176            vec![0x0d, 0xf2, 0x00, 0x1d],
9177            "add sp,sp,#256 must be ADDW (plain imm12), not a mis-encoded ADD.W"
9178        );
9179        // sub sp, sp, #256  →  SUBW (T4) SP, SP, #256  =  ad f2 00 1d
9180        assert_eq!(
9181            encoder
9182                .encode(&ArmOp::Sub {
9183                    rd: Reg::SP,
9184                    rn: Reg::SP,
9185                    op2: Operand2::Imm(256),
9186                })
9187                .unwrap(),
9188            vec![0xad, 0xf2, 0x00, 0x1d],
9189        );
9190        // > 4095 has no single-instruction encoding → error, not silent wrong.
9191        assert!(
9192            encoder
9193                .encode(&ArmOp::Add {
9194                    rd: Reg::SP,
9195                    rn: Reg::SP,
9196                    op2: Operand2::Imm(5000),
9197                })
9198                .is_err(),
9199            "add #5000 must error (no single ADDW), not mis-encode"
9200        );
9201    }
9202
9203    /// VCR-RA-001: ORR/EOR with a small immediate must encode the real
9204    /// instruction (not a silent `0xBF00` NOP). Pins the byte range and the
9205    /// Ok-or-Err bound that makes future Or/Eor immediate folding safe.
9206    #[test]
9207    fn orr_eor_immediate_encode_in_byte_range_else_error() {
9208        let encoder = ArmEncoder::new_thumb2();
9209        // orr r2, r0, #0x7e  →  ORR.W T1, imm8=0x7e
9210        assert_eq!(
9211            encoder
9212                .encode(&ArmOp::Orr {
9213                    rd: Reg::R2,
9214                    rn: Reg::R0,
9215                    op2: Operand2::Imm(0x7e),
9216                })
9217                .unwrap(),
9218            vec![0x40, 0xf0, 0x7e, 0x02],
9219        );
9220        // eor r2, r0, #0x7e  →  EOR.W T1, imm8=0x7e
9221        assert_eq!(
9222            encoder
9223                .encode(&ArmOp::Eor {
9224                    rd: Reg::R2,
9225                    rn: Reg::R0,
9226                    op2: Operand2::Imm(0x7e),
9227                })
9228                .unwrap(),
9229            vec![0x80, 0xf0, 0x7e, 0x02],
9230        );
9231        // Out-of-range immediates error rather than silently mis-encode / NOP.
9232        assert!(
9233            encoder
9234                .encode(&ArmOp::Orr {
9235                    rd: Reg::R2,
9236                    rn: Reg::R0,
9237                    op2: Operand2::Imm(0x140),
9238                })
9239                .is_err(),
9240            "ORR #0x140 must error, not emit a NOP"
9241        );
9242    }
9243
9244    #[test]
9245    fn test_encode_mve_different_qregs() {
9246        let encoder = ArmEncoder::new_thumb2();
9247
9248        // Test that different Q-register numbers produce different encodings
9249        let op1 = ArmOp::MveAddI {
9250            qd: QReg::Q0,
9251            qn: QReg::Q0,
9252            qm: QReg::Q0,
9253            size: MveSize::S32,
9254        };
9255        let op2 = ArmOp::MveAddI {
9256            qd: QReg::Q3,
9257            qn: QReg::Q5,
9258            qm: QReg::Q7,
9259            size: MveSize::S32,
9260        };
9261        let code1 = encoder.encode(&op1).unwrap();
9262        let code2 = encoder.encode(&op2).unwrap();
9263        assert_ne!(
9264            code1, code2,
9265            "Different Q-registers should produce different encodings"
9266        );
9267    }
9268
9269    #[test]
9270    fn test_encode_mve_arm32_nop() {
9271        // MVE instructions on ARM32 encoder should produce NOP (only Thumb-2 supported)
9272        let encoder = ArmEncoder::new_arm32();
9273        let op = ArmOp::MveAddI {
9274            qd: QReg::Q0,
9275            qn: QReg::Q1,
9276            qm: QReg::Q2,
9277            size: MveSize::S32,
9278        };
9279        let code = encoder.encode(&op).unwrap();
9280        assert_eq!(code.len(), 4, "ARM32 MVE should be 4 bytes (NOP)");
9281        // NOP in ARM32 is 0xE1A00000 (MOV R0, R0)
9282        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
9283        assert_eq!(instr, 0xE1A00000, "ARM32 MVE should encode as NOP");
9284    }
9285}